Sort multiple @Autowired methods on same bean class via ASM
Closes gh-30359
This commit is contained in:
		
							parent
							
								
									9333ed22f6
								
							
						
					
					
						commit
						7e6612a920
					
				|  | @ -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<String> lookupMethodsChecked = Collections.newSetFromMap(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); | ||||
| 		} | ||||
| 		this.beanFactory = clbf; | ||||
| 		this.metadataReaderFactory = new SimpleMetadataReaderFactory(clbf.getBeanClassLoader()); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
|  | @ -539,12 +548,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA | |||
| 			return InjectionMetadata.EMPTY; | ||||
| 		} | ||||
| 
 | ||||
| 		List<InjectionMetadata.InjectedElement> elements = new ArrayList<>(); | ||||
| 		final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>(); | ||||
| 		Class<?> targetClass = clazz; | ||||
| 
 | ||||
| 		do { | ||||
| 			final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>(); | ||||
| 
 | ||||
| 			final List<InjectionMetadata.InjectedElement> 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<InjectionMetadata.InjectedElement> 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<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. | ||||
| 	 */ | ||||
|  |  | |||
|  | @ -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; | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -403,11 +403,14 @@ class ConfigurationClassParser { | |||
| 						this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata(); | ||||
| 				Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName()); | ||||
| 				if (asmMethods.size() >= beanMethods.size()) { | ||||
| 					Set<MethodMetadata> candidateMethods = new LinkedHashSet<>(beanMethods); | ||||
| 					Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size()); | ||||
| 					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())) { | ||||
| 								selectedMethods.add(beanMethod); | ||||
| 								it.remove(); | ||||
| 								break; | ||||
| 							} | ||||
| 						} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue