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; | 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. | ||||||
| 	 */ | 	 */ | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
| 							} | 							} | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue