Skip to content

Commit b948237

Browse files
committed
Sort multiple @Autowired methods on same bean class via ASM
Closes gh-30359 (cherry picked from commit 7e6612a)
1 parent ad61fb7 commit b948237

File tree

4 files changed

+71
-17
lines changed

4 files changed

+71
-17
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;
@@ -62,6 +63,10 @@
6263
import org.springframework.core.annotation.AnnotationUtils;
6364
import org.springframework.core.annotation.MergedAnnotation;
6465
import org.springframework.core.annotation.MergedAnnotations;
66+
import org.springframework.core.type.AnnotationMetadata;
67+
import org.springframework.core.type.MethodMetadata;
68+
import org.springframework.core.type.classreading.MetadataReaderFactory;
69+
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
6570
import org.springframework.lang.Nullable;
6671
import org.springframework.util.Assert;
6772
import org.springframework.util.ClassUtils;
@@ -144,6 +149,9 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
144149
@Nullable
145150
private ConfigurableListableBeanFactory beanFactory;
146151

152+
@Nullable
153+
private MetadataReaderFactory metadataReaderFactory;
154+
147155
private final Set<String> lookupMethodsChecked = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
148156

149157
private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = new ConcurrentHashMap<>(256);
@@ -238,6 +246,7 @@ public void setBeanFactory(BeanFactory beanFactory) {
238246
"AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
239247
}
240248
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
249+
this.metadataReaderFactory = new SimpleMetadataReaderFactory(this.beanFactory.getBeanClassLoader());
241250
}
242251

243252

@@ -463,12 +472,11 @@ private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
463472
return InjectionMetadata.EMPTY;
464473
}
465474

466-
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
475+
final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
467476
Class<?> targetClass = clazz;
468477

469478
do {
470-
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
471-
479+
final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
472480
ReflectionUtils.doWithLocalFields(targetClass, field -> {
473481
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
474482
if (ann != null) {
@@ -479,10 +487,11 @@ private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
479487
return;
480488
}
481489
boolean required = determineRequiredStatus(ann);
482-
currElements.add(new AutowiredFieldElement(field, required));
490+
fieldElements.add(new AutowiredFieldElement(field, required));
483491
}
484492
});
485493

494+
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
486495
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
487496
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
488497
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
@@ -504,11 +513,12 @@ private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
504513
}
505514
boolean required = determineRequiredStatus(ann);
506515
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
507-
currElements.add(new AutowiredMethodElement(method, required, pd));
516+
methodElements.add(new AutowiredMethodElement(method, required, pd));
508517
}
509518
});
510519

511-
elements.addAll(0, currElements);
520+
elements.addAll(0, sortMethodElements(methodElements, targetClass));
521+
elements.addAll(0, fieldElements);
512522
targetClass = targetClass.getSuperclass();
513523
}
514524
while (targetClass != null && targetClass != Object.class);
@@ -573,6 +583,47 @@ protected <T> Map<String, T> findAutowireCandidates(Class<T> type) throws BeansE
573583
return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type);
574584
}
575585

586+
/**
587+
* Sort the method elements via ASM for deterministic declaration order if possible.
588+
*/
589+
private List<InjectionMetadata.InjectedElement> sortMethodElements(
590+
List<InjectionMetadata.InjectedElement> methodElements, Class<?> targetClass) {
591+
592+
if (this.metadataReaderFactory != null && methodElements.size() > 1) {
593+
// Try reading the class file via ASM for deterministic declaration order...
594+
// Unfortunately, the JVM's standard reflection returns methods in arbitrary
595+
// order, even between different runs of the same application on the same JVM.
596+
try {
597+
AnnotationMetadata asm =
598+
this.metadataReaderFactory.getMetadataReader(targetClass.getName()).getAnnotationMetadata();
599+
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Autowired.class.getName());
600+
if (asmMethods.size() >= methodElements.size()) {
601+
List<InjectionMetadata.InjectedElement> candidateMethods = new ArrayList<>(methodElements);
602+
List<InjectionMetadata.InjectedElement> selectedMethods = new ArrayList<>(asmMethods.size());
603+
for (MethodMetadata asmMethod : asmMethods) {
604+
for (Iterator<InjectionMetadata.InjectedElement> it = candidateMethods.iterator(); it.hasNext();) {
605+
InjectionMetadata.InjectedElement element = it.next();
606+
if (element.getMember().getName().equals(asmMethod.getMethodName())) {
607+
selectedMethods.add(element);
608+
it.remove();
609+
break;
610+
}
611+
}
612+
}
613+
if (selectedMethods.size() == methodElements.size()) {
614+
// All reflection-detected methods found in ASM method set -> proceed
615+
return selectedMethods;
616+
}
617+
}
618+
}
619+
catch (IOException ex) {
620+
logger.debug("Failed to read class file via ASM for determining @Autowired method order", ex);
621+
// No worries, let's continue with the reflection metadata we started with...
622+
}
623+
}
624+
return methodElements;
625+
}
626+
576627
/**
577628
* Register the specified bean as dependent on the autowired beans.
578629
*/

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import org.springframework.core.annotation.Order;
7474
import org.springframework.core.testfixture.io.SerializationTestUtils;
7575
import org.springframework.lang.Nullable;
76+
import org.springframework.util.Assert;
7677
import org.springframework.util.ObjectUtils;
7778
import org.springframework.util.ReflectionUtils;
7879

@@ -2620,13 +2621,12 @@ public static class ResourceInjectionBean {
26202621
@Autowired(required = false)
26212622
private TestBean testBean;
26222623

2623-
private TestBean testBean2;
2624+
TestBean testBean2;
26242625

26252626
@Autowired
26262627
public void setTestBean2(TestBean testBean2) {
2627-
if (this.testBean2 != null) {
2628-
throw new IllegalStateException("Already called");
2629-
}
2628+
Assert.state(this.testBean != null, "Wrong initialization order");
2629+
Assert.state(this.testBean2 == null, "Already called");
26302630
this.testBean2 = testBean2;
26312631
}
26322632

@@ -2661,7 +2661,7 @@ public NonPublicResourceInjectionBean() {
26612661
@Required
26622662
@SuppressWarnings("deprecation")
26632663
public void setTestBean2(TestBean testBean2) {
2664-
super.setTestBean2(testBean2);
2664+
this.testBean2 = testBean2;
26652665
}
26662666

26672667
@Autowired
@@ -2677,6 +2677,7 @@ private void inject(ITestBean testBean4) {
26772677

26782678
@Autowired
26792679
protected void initBeanFactory(BeanFactory beanFactory) {
2680+
Assert.state(this.baseInjected, "Wrong initialization order");
26802681
this.beanFactory = beanFactory;
26812682
}
26822683

@@ -4100,9 +4101,7 @@ public static abstract class Foo<T extends Runnable, RT extends Callable<T>> {
41004101
private RT obj;
41014102

41024103
protected void setObj(RT obj) {
4103-
if (this.obj != null) {
4104-
throw new IllegalStateException("Already called");
4105-
}
4104+
Assert.state(this.obj == null, "Already called");
41064105
this.obj = obj;
41074106
}
41084107
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 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.
@@ -424,6 +424,7 @@ public ConfigurationClassBeanDefinition(
424424

425425
public ConfigurationClassBeanDefinition(RootBeanDefinition original,
426426
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) {
427+
427428
super(original);
428429
this.annotationMetadata = configClass.getMetadata();
429430
this.factoryMethodMetadata = beanMethodMetadata;

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

Lines changed: 5 additions & 2 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.
@@ -409,11 +409,14 @@ private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass)
409409
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
410410
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
411411
if (asmMethods.size() >= beanMethods.size()) {
412+
Set<MethodMetadata> candidateMethods = new LinkedHashSet<>(beanMethods);
412413
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
413414
for (MethodMetadata asmMethod : asmMethods) {
414-
for (MethodMetadata beanMethod : beanMethods) {
415+
for (Iterator<MethodMetadata> it = candidateMethods.iterator(); it.hasNext();) {
416+
MethodMetadata beanMethod = it.next();
415417
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
416418
selectedMethods.add(beanMethod);
419+
it.remove();
417420
break;
418421
}
419422
}

0 commit comments

Comments
 (0)