Skip to content

Commit 7e6612a

Browse files
committed
Sort multiple @Autowired methods on same bean class via ASM
Closes gh-30359
1 parent 9333ed2 commit 7e6612a

File tree

4 files changed

+71
-19
lines changed

4 files changed

+71
-19
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.beans.factory.annotation;
1818

1919
import java.beans.PropertyDescriptor;
20+
import java.io.IOException;
2021
import java.lang.annotation.Annotation;
2122
import java.lang.reflect.AccessibleObject;
2223
import java.lang.reflect.Constructor;
@@ -79,6 +80,10 @@
7980
import org.springframework.core.annotation.AnnotationUtils;
8081
import org.springframework.core.annotation.MergedAnnotation;
8182
import org.springframework.core.annotation.MergedAnnotations;
83+
import org.springframework.core.type.AnnotationMetadata;
84+
import org.springframework.core.type.MethodMetadata;
85+
import org.springframework.core.type.classreading.MetadataReaderFactory;
86+
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
8287
import org.springframework.javapoet.ClassName;
8388
import org.springframework.javapoet.CodeBlock;
8489
import org.springframework.lang.Nullable;
@@ -167,6 +172,9 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
167172
@Nullable
168173
private ConfigurableListableBeanFactory beanFactory;
169174

175+
@Nullable
176+
private MetadataReaderFactory metadataReaderFactory;
177+
170178
private final Set<String> lookupMethodsChecked = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
171179

172180
private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = new ConcurrentHashMap<>(256);
@@ -271,6 +279,7 @@ public void setBeanFactory(BeanFactory beanFactory) {
271279
"AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
272280
}
273281
this.beanFactory = clbf;
282+
this.metadataReaderFactory = new SimpleMetadataReaderFactory(clbf.getBeanClassLoader());
274283
}
275284

276285

@@ -539,12 +548,11 @@ private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
539548
return InjectionMetadata.EMPTY;
540549
}
541550

542-
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
551+
final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
543552
Class<?> targetClass = clazz;
544553

545554
do {
546-
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
547-
555+
final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
548556
ReflectionUtils.doWithLocalFields(targetClass, field -> {
549557
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
550558
if (ann != null) {
@@ -555,10 +563,11 @@ private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
555563
return;
556564
}
557565
boolean required = determineRequiredStatus(ann);
558-
currElements.add(new AutowiredFieldElement(field, required));
566+
fieldElements.add(new AutowiredFieldElement(field, required));
559567
}
560568
});
561569

570+
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
562571
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
563572
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
564573
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
@@ -580,11 +589,12 @@ private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
580589
}
581590
boolean required = determineRequiredStatus(ann);
582591
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
583-
currElements.add(new AutowiredMethodElement(method, required, pd));
592+
methodElements.add(new AutowiredMethodElement(method, required, pd));
584593
}
585594
});
586595

587-
elements.addAll(0, currElements);
596+
elements.addAll(0, sortMethodElements(methodElements, targetClass));
597+
elements.addAll(0, fieldElements);
588598
targetClass = targetClass.getSuperclass();
589599
}
590600
while (targetClass != null && targetClass != Object.class);
@@ -617,6 +627,47 @@ protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
617627
this.requiredParameterValue == ann.getBoolean(this.requiredParameterName));
618628
}
619629

630+
/**
631+
* Sort the method elements via ASM for deterministic declaration order if possible.
632+
*/
633+
private List<InjectionMetadata.InjectedElement> sortMethodElements(
634+
List<InjectionMetadata.InjectedElement> methodElements, Class<?> targetClass) {
635+
636+
if (this.metadataReaderFactory != null && methodElements.size() > 1) {
637+
// Try reading the class file via ASM for deterministic declaration order...
638+
// Unfortunately, the JVM's standard reflection returns methods in arbitrary
639+
// order, even between different runs of the same application on the same JVM.
640+
try {
641+
AnnotationMetadata asm =
642+
this.metadataReaderFactory.getMetadataReader(targetClass.getName()).getAnnotationMetadata();
643+
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Autowired.class.getName());
644+
if (asmMethods.size() >= methodElements.size()) {
645+
List<InjectionMetadata.InjectedElement> candidateMethods = new ArrayList<>(methodElements);
646+
List<InjectionMetadata.InjectedElement> selectedMethods = new ArrayList<>(asmMethods.size());
647+
for (MethodMetadata asmMethod : asmMethods) {
648+
for (Iterator<InjectionMetadata.InjectedElement> it = candidateMethods.iterator(); it.hasNext();) {
649+
InjectionMetadata.InjectedElement element = it.next();
650+
if (element.getMember().getName().equals(asmMethod.getMethodName())) {
651+
selectedMethods.add(element);
652+
it.remove();
653+
break;
654+
}
655+
}
656+
}
657+
if (selectedMethods.size() == methodElements.size()) {
658+
// All reflection-detected methods found in ASM method set -> proceed
659+
return selectedMethods;
660+
}
661+
}
662+
}
663+
catch (IOException ex) {
664+
logger.debug("Failed to read class file via ASM for determining @Autowired method order", ex);
665+
// No worries, let's continue with the reflection metadata we started with...
666+
}
667+
}
668+
return methodElements;
669+
}
670+
620671
/**
621672
* Register the specified bean as dependent on the autowired beans.
622673
*/

spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import org.springframework.core.annotation.Order;
7373
import org.springframework.core.testfixture.io.SerializationTestUtils;
7474
import org.springframework.lang.Nullable;
75+
import org.springframework.util.Assert;
7576
import org.springframework.util.ObjectUtils;
7677
import org.springframework.util.ReflectionUtils;
7778

@@ -2605,13 +2606,12 @@ public static class ResourceInjectionBean {
26052606
@Autowired(required = false)
26062607
private TestBean testBean;
26072608

2608-
private TestBean testBean2;
2609+
TestBean testBean2;
26092610

26102611
@Autowired
26112612
public void setTestBean2(TestBean testBean2) {
2612-
if (this.testBean2 != null) {
2613-
throw new IllegalStateException("Already called");
2614-
}
2613+
Assert.state(this.testBean != null, "Wrong initialization order");
2614+
Assert.state(this.testBean2 == null, "Already called");
26152615
this.testBean2 = testBean2;
26162616
}
26172617

@@ -2643,9 +2643,8 @@ public NonPublicResourceInjectionBean() {
26432643

26442644
@Override
26452645
@Autowired
2646-
@SuppressWarnings("deprecation")
26472646
public void setTestBean2(TestBean testBean2) {
2648-
super.setTestBean2(testBean2);
2647+
this.testBean2 = testBean2;
26492648
}
26502649

26512650
@Autowired
@@ -2661,6 +2660,7 @@ private void inject(ITestBean testBean4) {
26612660

26622661
@Autowired
26632662
protected void initBeanFactory(BeanFactory beanFactory) {
2663+
Assert.state(this.baseInjected, "Wrong initialization order");
26642664
this.beanFactory = beanFactory;
26652665
}
26662666

@@ -4084,9 +4084,7 @@ public static abstract class Foo<T extends Runnable, RT extends Callable<T>> {
40844084
private RT obj;
40854085

40864086
protected void setObj(RT obj) {
4087-
if (this.obj != null) {
4088-
throw new IllegalStateException("Already called");
4089-
}
4087+
Assert.state(this.obj == null, "Already called");
40904088
this.obj = obj;
40914089
}
40924090
}

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -314,8 +314,7 @@ protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String
314314

315315
// At this point, it's a top-level override (probably XML), just having been parsed
316316
// before configuration class processing kicks in...
317-
if (this.registry instanceof DefaultListableBeanFactory dlbf &&
318-
!dlbf.isAllowBeanDefinitionOverriding()) {
317+
if (this.registry instanceof DefaultListableBeanFactory dlbf && !dlbf.isAllowBeanDefinitionOverriding()) {
319318
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
320319
beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
321320
}
@@ -401,6 +400,7 @@ public ConfigurationClassBeanDefinition(
401400

402401
public ConfigurationClassBeanDefinition(RootBeanDefinition original,
403402
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) {
403+
404404
super(original);
405405
this.annotationMetadata = configClass.getMetadata();
406406
this.factoryMethodMetadata = beanMethodMetadata;

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,11 +403,14 @@ private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass)
403403
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
404404
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
405405
if (asmMethods.size() >= beanMethods.size()) {
406+
Set<MethodMetadata> candidateMethods = new LinkedHashSet<>(beanMethods);
406407
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
407408
for (MethodMetadata asmMethod : asmMethods) {
408-
for (MethodMetadata beanMethod : beanMethods) {
409+
for (Iterator<MethodMetadata> it = candidateMethods.iterator(); it.hasNext();) {
410+
MethodMetadata beanMethod = it.next();
409411
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
410412
selectedMethods.add(beanMethod);
413+
it.remove();
411414
break;
412415
}
413416
}

0 commit comments

Comments
 (0)