Introduce shortcut for declared dependency name matching target bean name
Closes gh-28122
This commit is contained in:
		
							parent
							
								
									eefdee7983
								
							
						
					
					
						commit
						c6146ea2db
					
				|  | @ -1,5 +1,5 @@ | ||||||
| /* | /* | ||||||
|  * Copyright 2002-2023 the original author or authors. |  * Copyright 2002-2024 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. | ||||||
|  | @ -29,6 +29,7 @@ import java.lang.reflect.Method; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.Comparator; | import java.util.Comparator; | ||||||
| import java.util.IdentityHashMap; | import java.util.IdentityHashMap; | ||||||
| import java.util.Iterator; | import java.util.Iterator; | ||||||
|  | @ -167,6 +168,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto | ||||||
| 	/** Map from bean name to merged BeanDefinitionHolder. */ | 	/** Map from bean name to merged BeanDefinitionHolder. */ | ||||||
| 	private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256); | 	private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256); | ||||||
| 
 | 
 | ||||||
|  | 	// Set of bean definition names with a primary marker. */ | ||||||
|  | 	private final Set<String> primaryBeanNames = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); | ||||||
|  | 
 | ||||||
| 	/** Map of singleton and non-singleton bean names, keyed by dependency type. */ | 	/** Map of singleton and non-singleton bean names, keyed by dependency type. */ | ||||||
| 	private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64); | 	private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64); | ||||||
| 
 | 
 | ||||||
|  | @ -1084,6 +1088,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto | ||||||
| 		else if (isConfigurationFrozen()) { | 		else if (isConfigurationFrozen()) { | ||||||
| 			clearByTypeCache(); | 			clearByTypeCache(); | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
|  | 		// Cache a primary marker for the given bean. | ||||||
|  | 		if (beanDefinition.isPrimary()) { | ||||||
|  | 			this.primaryBeanNames.add(beanName); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Override | 	@Override | ||||||
|  | @ -1135,6 +1144,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto | ||||||
| 		// (e.g. the default StaticMessageSource in a StaticApplicationContext). | 		// (e.g. the default StaticMessageSource in a StaticApplicationContext). | ||||||
| 		destroySingleton(beanName); | 		destroySingleton(beanName); | ||||||
| 
 | 
 | ||||||
|  | 		// Remove a cached primary marker for the given bean. | ||||||
|  | 		this.primaryBeanNames.remove(beanName); | ||||||
|  | 
 | ||||||
| 		// Notify all post-processors that the specified bean definition has been reset. | 		// Notify all post-processors that the specified bean definition has been reset. | ||||||
| 		for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) { | 		for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) { | ||||||
| 			processor.resetBeanDefinition(beanName); | 			processor.resetBeanDefinition(beanName); | ||||||
|  | @ -1388,15 +1400,27 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// Step 3a: multiple beans as stream / array / standard collection / plain map | 			// Step 3: shortcut for declared dependency name matching target bean name | ||||||
|  | 			String dependencyName = descriptor.getDependencyName(); | ||||||
|  | 			if (dependencyName != null && containsBean(dependencyName) && | ||||||
|  | 					isTypeMatch(dependencyName, type) && isAutowireCandidate(dependencyName, descriptor) && | ||||||
|  | 					!hasPrimaryConflict(dependencyName, type) && !isSelfReference(beanName, dependencyName)) { | ||||||
|  | 				if (autowiredBeanNames != null) { | ||||||
|  | 					autowiredBeanNames.add(dependencyName); | ||||||
|  | 				} | ||||||
|  | 				Object dependencyBean = getBean(dependencyName); | ||||||
|  | 				return resolveInstance(dependencyBean, descriptor, type, dependencyName); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Step 4a: multiple beans as stream / array / standard collection / plain map | ||||||
| 			Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter); | 			Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter); | ||||||
| 			if (multipleBeans != null) { | 			if (multipleBeans != null) { | ||||||
| 				return multipleBeans; | 				return multipleBeans; | ||||||
| 			} | 			} | ||||||
| 			// Step 3b: direct bean matches, possibly direct beans of type Collection / Map | 			// Step 4b: direct bean matches, possibly direct beans of type Collection / Map | ||||||
| 			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); | 			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); | ||||||
| 			if (matchingBeans.isEmpty()) { | 			if (matchingBeans.isEmpty()) { | ||||||
| 				// Step 3c (fallback): custom Collection / Map declarations for collecting multiple beans | 				// Step 4c (fallback): custom Collection / Map declarations for collecting multiple beans | ||||||
| 				multipleBeans = resolveMultipleBeansFallback(descriptor, beanName, autowiredBeanNames, typeConverter); | 				multipleBeans = resolveMultipleBeansFallback(descriptor, beanName, autowiredBeanNames, typeConverter); | ||||||
| 				if (multipleBeans != null) { | 				if (multipleBeans != null) { | ||||||
| 					return multipleBeans; | 					return multipleBeans; | ||||||
|  | @ -1411,7 +1435,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto | ||||||
| 			String autowiredBeanName; | 			String autowiredBeanName; | ||||||
| 			Object instanceCandidate; | 			Object instanceCandidate; | ||||||
| 
 | 
 | ||||||
| 			// Step 4: determine single candidate | 			// Step 5: determine single candidate | ||||||
| 			if (matchingBeans.size() > 1) { | 			if (matchingBeans.size() > 1) { | ||||||
| 				autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); | 				autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); | ||||||
| 				if (autowiredBeanName == null) { | 				if (autowiredBeanName == null) { | ||||||
|  | @ -1435,31 +1459,37 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto | ||||||
| 				instanceCandidate = entry.getValue(); | 				instanceCandidate = entry.getValue(); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// Step 5: validate single result | 			// Step 6: validate single result | ||||||
| 			if (autowiredBeanNames != null) { | 			if (autowiredBeanNames != null) { | ||||||
| 				autowiredBeanNames.add(autowiredBeanName); | 				autowiredBeanNames.add(autowiredBeanName); | ||||||
| 			} | 			} | ||||||
| 			if (instanceCandidate instanceof Class) { | 			if (instanceCandidate instanceof Class) { | ||||||
| 				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); | 				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); | ||||||
| 			} | 			} | ||||||
| 			Object result = instanceCandidate; | 			return resolveInstance(instanceCandidate, descriptor, type, autowiredBeanName); | ||||||
| 			if (result instanceof NullBean) { |  | ||||||
| 				if (isRequired(descriptor)) { |  | ||||||
| 					// Raise exception if null encountered for required injection point |  | ||||||
| 					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); |  | ||||||
| 				} |  | ||||||
| 				result = null; |  | ||||||
| 			} |  | ||||||
| 			if (!ClassUtils.isAssignableValue(type, result)) { |  | ||||||
| 				throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass()); |  | ||||||
| 			} |  | ||||||
| 			return result; |  | ||||||
| 		} | 		} | ||||||
| 		finally { | 		finally { | ||||||
| 			ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); | 			ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Nullable | ||||||
|  | 	private Object resolveInstance(Object candidate, DependencyDescriptor descriptor, Class<?> type, String name) { | ||||||
|  | 		Object result = candidate; | ||||||
|  | 		if (result instanceof NullBean) { | ||||||
|  | 			// Raise exception if null encountered for required injection point | ||||||
|  | 			if (isRequired(descriptor)) { | ||||||
|  | 				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); | ||||||
|  | 			} | ||||||
|  | 			result = null; | ||||||
|  | 		} | ||||||
|  | 		if (!ClassUtils.isAssignableValue(type, result)) { | ||||||
|  | 			throw new BeanNotOfRequiredTypeException(name, type, candidate.getClass()); | ||||||
|  | 		} | ||||||
|  | 		return result; | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@Nullable | 	@Nullable | ||||||
| 	private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName, | 	private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName, | ||||||
| 			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) { | 			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) { | ||||||
|  | @ -1712,20 +1742,27 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto | ||||||
| 	@Nullable | 	@Nullable | ||||||
| 	protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) { | 	protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) { | ||||||
| 		Class<?> requiredType = descriptor.getDependencyType(); | 		Class<?> requiredType = descriptor.getDependencyType(); | ||||||
|  | 		// Step 1: check primary candidate | ||||||
| 		String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); | 		String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); | ||||||
| 		if (primaryCandidate != null) { | 		if (primaryCandidate != null) { | ||||||
| 			return primaryCandidate; | 			return primaryCandidate; | ||||||
| 		} | 		} | ||||||
|  | 		// Step 2: check bean name match | ||||||
|  | 		for (String candidateName : candidates.keySet()) { | ||||||
|  | 			if (matchesBeanName(candidateName, descriptor.getDependencyName())) { | ||||||
|  | 				return candidateName; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// Step 3: check highest priority candidate | ||||||
| 		String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); | 		String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); | ||||||
| 		if (priorityCandidate != null) { | 		if (priorityCandidate != null) { | ||||||
| 			return priorityCandidate; | 			return priorityCandidate; | ||||||
| 		} | 		} | ||||||
| 		// Fallback: pick directly registered dependency or qualified bean name match | 		// Step 4: pick directly registered dependency | ||||||
| 		for (Map.Entry<String, Object> entry : candidates.entrySet()) { | 		for (Map.Entry<String, Object> entry : candidates.entrySet()) { | ||||||
| 			String candidateName = entry.getKey(); | 			String candidateName = entry.getKey(); | ||||||
| 			Object beanInstance = entry.getValue(); | 			Object beanInstance = entry.getValue(); | ||||||
| 			if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) || | 			if (beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) { | ||||||
| 					matchesBeanName(candidateName, descriptor.getDependencyName())) { |  | ||||||
| 				return candidateName; | 				return candidateName; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | @ -1866,6 +1903,21 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto | ||||||
| 						beanName.equals(getMergedLocalBeanDefinition(candidateName).getFactoryBeanName())))); | 						beanName.equals(getMergedLocalBeanDefinition(candidateName).getFactoryBeanName())))); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Determine whether there is a primary bean registered for the given dependency type, | ||||||
|  | 	 * not matching the given bean name. | ||||||
|  | 	 */ | ||||||
|  | 	@Nullable | ||||||
|  | 	private boolean hasPrimaryConflict(String beanName, Class<?> dependencyType) { | ||||||
|  | 		for (String candidate : this.primaryBeanNames) { | ||||||
|  | 			if (isTypeMatch(candidate, dependencyType) && !candidate.equals(beanName)) { | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return (getParentBeanFactory() instanceof DefaultListableBeanFactory parent && | ||||||
|  | 				parent.hasPrimaryConflict(beanName, dependencyType)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * Raise a NoSuchBeanDefinitionException or BeanNotOfRequiredTypeException | 	 * Raise a NoSuchBeanDefinitionException or BeanNotOfRequiredTypeException | ||||||
| 	 * for an unresolvable dependency. | 	 * for an unresolvable dependency. | ||||||
|  |  | ||||||
|  | @ -18,9 +18,7 @@ package org.springframework.beans.factory; | ||||||
| 
 | 
 | ||||||
| import java.io.Closeable; | import java.io.Closeable; | ||||||
| import java.io.Serializable; | import java.io.Serializable; | ||||||
| import java.lang.reflect.Constructor; |  | ||||||
| import java.lang.reflect.Field; | import java.lang.reflect.Field; | ||||||
| import java.lang.reflect.Method; |  | ||||||
| import java.net.MalformedURLException; | import java.net.MalformedURLException; | ||||||
| import java.text.NumberFormat; | import java.text.NumberFormat; | ||||||
| import java.text.ParseException; | import java.text.ParseException; | ||||||
|  | @ -81,7 +79,6 @@ import org.springframework.beans.testfixture.beans.factory.DummyFactory; | ||||||
| import org.springframework.core.DefaultParameterNameDiscoverer; | import org.springframework.core.DefaultParameterNameDiscoverer; | ||||||
| import org.springframework.core.MethodParameter; | import org.springframework.core.MethodParameter; | ||||||
| import org.springframework.core.Ordered; | import org.springframework.core.Ordered; | ||||||
| import org.springframework.core.ParameterNameDiscoverer; |  | ||||||
| import org.springframework.core.ResolvableType; | import org.springframework.core.ResolvableType; | ||||||
| import org.springframework.core.annotation.AnnotationAwareOrderComparator; | import org.springframework.core.annotation.AnnotationAwareOrderComparator; | ||||||
| import org.springframework.core.annotation.Order; | import org.springframework.core.annotation.Order; | ||||||
|  | @ -121,20 +118,6 @@ class DefaultListableBeanFactoryTests { | ||||||
| 
 | 
 | ||||||
| 	private final DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); | 	private final DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); | ||||||
| 
 | 
 | ||||||
| 	{ |  | ||||||
| 		// No parameter name discovery expected unless named arguments are used |  | ||||||
| 		lbf.setParameterNameDiscoverer(new ParameterNameDiscoverer() { |  | ||||||
| 			@Override |  | ||||||
| 			public String[] getParameterNames(Method method) { |  | ||||||
| 				throw new UnsupportedOperationException(); |  | ||||||
| 			} |  | ||||||
| 			@Override |  | ||||||
| 			public String[] getParameterNames(Constructor<?> ctor) { |  | ||||||
| 				throw new UnsupportedOperationException(); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	void unreferencedSingletonWasInstantiated() { | 	void unreferencedSingletonWasInstantiated() { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue