Sort multiple @Autowired methods on same bean class via ASM

Closes gh-30359
This commit is contained in:
Juergen Hoeller 2023-08-04 00:47:18 +02:00
parent 9333ed22f6
commit 7e6612a920
4 changed files with 71 additions and 19 deletions

View File

@ -17,6 +17,7 @@
package org.springframework.beans.factory.annotation; package org.springframework.beans.factory.annotation;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject; import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor; 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.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations; 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.ClassName;
import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -167,6 +172,9 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
@Nullable @Nullable
private ConfigurableListableBeanFactory beanFactory; private ConfigurableListableBeanFactory beanFactory;
@Nullable
private MetadataReaderFactory metadataReaderFactory;
private final Set<String> lookupMethodsChecked = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); private final Set<String> lookupMethodsChecked = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = new ConcurrentHashMap<>(256); private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = new ConcurrentHashMap<>(256);
@ -271,6 +279,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
"AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
} }
this.beanFactory = clbf; this.beanFactory = clbf;
this.metadataReaderFactory = new SimpleMetadataReaderFactory(clbf.getBeanClassLoader());
} }
@ -539,12 +548,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return InjectionMetadata.EMPTY; return InjectionMetadata.EMPTY;
} }
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>(); final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz; Class<?> targetClass = clazz;
do { do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>(); final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> { ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field); MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) { if (ann != null) {
@ -555,10 +563,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return; return;
} }
boolean required = determineRequiredStatus(ann); boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required)); fieldElements.add(new AutowiredFieldElement(field, required));
} }
}); });
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> { ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
@ -580,11 +589,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
} }
boolean required = determineRequiredStatus(ann); boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); 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(); targetClass = targetClass.getSuperclass();
} }
while (targetClass != null && targetClass != Object.class); while (targetClass != null && targetClass != Object.class);
@ -617,6 +627,47 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
this.requiredParameterValue == ann.getBoolean(this.requiredParameterName)); this.requiredParameterValue == ann.getBoolean(this.requiredParameterName));
} }
/**
* Sort the method elements via ASM for deterministic declaration order if possible.
*/
private List<InjectionMetadata.InjectedElement> sortMethodElements(
List<InjectionMetadata.InjectedElement> 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<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Autowired.class.getName());
if (asmMethods.size() >= methodElements.size()) {
List<InjectionMetadata.InjectedElement> candidateMethods = new ArrayList<>(methodElements);
List<InjectionMetadata.InjectedElement> selectedMethods = new ArrayList<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
for (Iterator<InjectionMetadata.InjectedElement> 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. * Register the specified bean as dependent on the autowired beans.
*/ */

View File

@ -72,6 +72,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.testfixture.io.SerializationTestUtils; import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@ -2605,13 +2606,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Autowired(required = false) @Autowired(required = false)
private TestBean testBean; private TestBean testBean;
private TestBean testBean2; TestBean testBean2;
@Autowired @Autowired
public void setTestBean2(TestBean testBean2) { public void setTestBean2(TestBean testBean2) {
if (this.testBean2 != null) { Assert.state(this.testBean != null, "Wrong initialization order");
throw new IllegalStateException("Already called"); Assert.state(this.testBean2 == null, "Already called");
}
this.testBean2 = testBean2; this.testBean2 = testBean2;
} }
@ -2643,9 +2643,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Override @Override
@Autowired @Autowired
@SuppressWarnings("deprecation")
public void setTestBean2(TestBean testBean2) { public void setTestBean2(TestBean testBean2) {
super.setTestBean2(testBean2); this.testBean2 = testBean2;
} }
@Autowired @Autowired
@ -2661,6 +2660,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Autowired @Autowired
protected void initBeanFactory(BeanFactory beanFactory) { protected void initBeanFactory(BeanFactory beanFactory) {
Assert.state(this.baseInjected, "Wrong initialization order");
this.beanFactory = beanFactory; this.beanFactory = beanFactory;
} }
@ -4084,9 +4084,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
private RT obj; private RT obj;
protected void setObj(RT obj) { protected void setObj(RT obj) {
if (this.obj != null) { Assert.state(this.obj == null, "Already called");
throw new IllegalStateException("Already called");
}
this.obj = obj; this.obj = obj;
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 // At this point, it's a top-level override (probably XML), just having been parsed
// before configuration class processing kicks in... // before configuration class processing kicks in...
if (this.registry instanceof DefaultListableBeanFactory dlbf && if (this.registry instanceof DefaultListableBeanFactory dlbf && !dlbf.isAllowBeanDefinitionOverriding()) {
!dlbf.isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(), throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef); beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
} }
@ -401,6 +400,7 @@ class ConfigurationClassBeanDefinitionReader {
public ConfigurationClassBeanDefinition(RootBeanDefinition original, public ConfigurationClassBeanDefinition(RootBeanDefinition original,
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) { ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) {
super(original); super(original);
this.annotationMetadata = configClass.getMetadata(); this.annotationMetadata = configClass.getMetadata();
this.factoryMethodMetadata = beanMethodMetadata; this.factoryMethodMetadata = beanMethodMetadata;

View File

@ -403,11 +403,14 @@ class ConfigurationClassParser {
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata(); this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName()); Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
if (asmMethods.size() >= beanMethods.size()) { if (asmMethods.size() >= beanMethods.size()) {
Set<MethodMetadata> candidateMethods = new LinkedHashSet<>(beanMethods);
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size()); Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) { for (MethodMetadata asmMethod : asmMethods) {
for (MethodMetadata beanMethod : beanMethods) { for (Iterator<MethodMetadata> it = candidateMethods.iterator(); it.hasNext();) {
MethodMetadata beanMethod = it.next();
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) { if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
selectedMethods.add(beanMethod); selectedMethods.add(beanMethod);
it.remove();
break; break;
} }
} }