diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index da11e0203f5..a26094cd25d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -17,6 +17,7 @@ package org.springframework.beans.factory.annotation; import java.beans.PropertyDescriptor; +import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; @@ -79,6 +80,10 @@ import org.springframework.core.PriorityOrdered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.javapoet.ClassName; import org.springframework.javapoet.CodeBlock; import org.springframework.lang.Nullable; @@ -167,6 +172,9 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA @Nullable private ConfigurableListableBeanFactory beanFactory; + @Nullable + private MetadataReaderFactory metadataReaderFactory; + private final Set lookupMethodsChecked = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); private final Map, Constructor[]> candidateConstructorsCache = new ConcurrentHashMap<>(256); @@ -271,6 +279,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); } this.beanFactory = clbf; + this.metadataReaderFactory = new SimpleMetadataReaderFactory(clbf.getBeanClassLoader()); } @@ -539,12 +548,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA return InjectionMetadata.EMPTY; } - List elements = new ArrayList<>(); + final List elements = new ArrayList<>(); Class targetClass = clazz; do { - final List currElements = new ArrayList<>(); - + final List fieldElements = new ArrayList<>(); ReflectionUtils.doWithLocalFields(targetClass, field -> { MergedAnnotation ann = findAutowiredAnnotation(field); if (ann != null) { @@ -555,10 +563,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA return; } boolean required = determineRequiredStatus(ann); - currElements.add(new AutowiredFieldElement(field, required)); + fieldElements.add(new AutowiredFieldElement(field, required)); } }); + final List methodElements = new ArrayList<>(); ReflectionUtils.doWithLocalMethods(targetClass, method -> { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { @@ -580,11 +589,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA } boolean required = determineRequiredStatus(ann); PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); - currElements.add(new AutowiredMethodElement(method, required, pd)); + methodElements.add(new AutowiredMethodElement(method, required, pd)); } }); - elements.addAll(0, currElements); + elements.addAll(0, sortMethodElements(methodElements, targetClass)); + elements.addAll(0, fieldElements); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); @@ -617,6 +627,47 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA this.requiredParameterValue == ann.getBoolean(this.requiredParameterName)); } + /** + * Sort the method elements via ASM for deterministic declaration order if possible. + */ + private List sortMethodElements( + List methodElements, Class targetClass) { + + if (this.metadataReaderFactory != null && methodElements.size() > 1) { + // Try reading the class file via ASM for deterministic declaration order... + // Unfortunately, the JVM's standard reflection returns methods in arbitrary + // order, even between different runs of the same application on the same JVM. + try { + AnnotationMetadata asm = + this.metadataReaderFactory.getMetadataReader(targetClass.getName()).getAnnotationMetadata(); + Set asmMethods = asm.getAnnotatedMethods(Autowired.class.getName()); + if (asmMethods.size() >= methodElements.size()) { + List candidateMethods = new ArrayList<>(methodElements); + List selectedMethods = new ArrayList<>(asmMethods.size()); + for (MethodMetadata asmMethod : asmMethods) { + for (Iterator it = candidateMethods.iterator(); it.hasNext();) { + InjectionMetadata.InjectedElement element = it.next(); + if (element.getMember().getName().equals(asmMethod.getMethodName())) { + selectedMethods.add(element); + it.remove(); + break; + } + } + } + if (selectedMethods.size() == methodElements.size()) { + // All reflection-detected methods found in ASM method set -> proceed + return selectedMethods; + } + } + } + catch (IOException ex) { + logger.debug("Failed to read class file via ASM for determining @Autowired method order", ex); + // No worries, let's continue with the reflection metadata we started with... + } + } + return methodElements; + } + /** * Register the specified bean as dependent on the autowired beans. */ diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 3a1fb0a6139..289863e6d1c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -72,6 +72,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.Order; import org.springframework.core.testfixture.io.SerializationTestUtils; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; @@ -2605,13 +2606,12 @@ public class AutowiredAnnotationBeanPostProcessorTests { @Autowired(required = false) private TestBean testBean; - private TestBean testBean2; + TestBean testBean2; @Autowired public void setTestBean2(TestBean testBean2) { - if (this.testBean2 != null) { - throw new IllegalStateException("Already called"); - } + Assert.state(this.testBean != null, "Wrong initialization order"); + Assert.state(this.testBean2 == null, "Already called"); this.testBean2 = testBean2; } @@ -2643,9 +2643,8 @@ public class AutowiredAnnotationBeanPostProcessorTests { @Override @Autowired - @SuppressWarnings("deprecation") public void setTestBean2(TestBean testBean2) { - super.setTestBean2(testBean2); + this.testBean2 = testBean2; } @Autowired @@ -2661,6 +2660,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { @Autowired protected void initBeanFactory(BeanFactory beanFactory) { + Assert.state(this.baseInjected, "Wrong initialization order"); this.beanFactory = beanFactory; } @@ -4084,9 +4084,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { private RT obj; protected void setObj(RT obj) { - if (this.obj != null) { - throw new IllegalStateException("Already called"); - } + Assert.state(this.obj == null, "Already called"); this.obj = obj; } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 66df6f8bcfc..ae75eb1a017 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -314,8 +314,7 @@ class ConfigurationClassBeanDefinitionReader { // At this point, it's a top-level override (probably XML), just having been parsed // before configuration class processing kicks in... - if (this.registry instanceof DefaultListableBeanFactory dlbf && - !dlbf.isAllowBeanDefinitionOverriding()) { + if (this.registry instanceof DefaultListableBeanFactory dlbf && !dlbf.isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(), beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef); } @@ -401,6 +400,7 @@ class ConfigurationClassBeanDefinitionReader { public ConfigurationClassBeanDefinition(RootBeanDefinition original, ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) { + super(original); this.annotationMetadata = configClass.getMetadata(); this.factoryMethodMetadata = beanMethodMetadata; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index afcc46a27f2..12281caec79 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -403,11 +403,14 @@ class ConfigurationClassParser { this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata(); Set asmMethods = asm.getAnnotatedMethods(Bean.class.getName()); if (asmMethods.size() >= beanMethods.size()) { + Set candidateMethods = new LinkedHashSet<>(beanMethods); Set selectedMethods = new LinkedHashSet<>(asmMethods.size()); for (MethodMetadata asmMethod : asmMethods) { - for (MethodMetadata beanMethod : beanMethods) { + for (Iterator it = candidateMethods.iterator(); it.hasNext();) { + MethodMetadata beanMethod = it.next(); if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) { selectedMethods.add(beanMethod); + it.remove(); break; } }