Introduce shortcut for declared dependency name matching target bean name

Closes gh-28122
This commit is contained in:
Juergen Hoeller 2024-02-16 21:28:04 +01:00
parent eefdee7983
commit c6146ea2db
2 changed files with 73 additions and 38 deletions

View File

@ -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.

View File

@ -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() {