Skip to content

Commit c6146ea

Browse files
committed
Introduce shortcut for declared dependency name matching target bean name
Closes gh-28122
1 parent eefdee7 commit c6146ea

File tree

2 files changed

+73
-38
lines changed

2 files changed

+73
-38
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

+73-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
import java.util.ArrayList;
3030
import java.util.Arrays;
3131
import java.util.Collection;
32+
import java.util.Collections;
3233
import java.util.Comparator;
3334
import java.util.IdentityHashMap;
3435
import java.util.Iterator;
@@ -167,6 +168,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
167168
/** Map from bean name to merged BeanDefinitionHolder. */
168169
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
169170

171+
// Set of bean definition names with a primary marker. */
172+
private final Set<String> primaryBeanNames = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
173+
170174
/** Map of singleton and non-singleton bean names, keyed by dependency type. */
171175
private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64);
172176

@@ -1084,6 +1088,11 @@ else if (!beanDefinition.equals(existingDefinition)) {
10841088
else if (isConfigurationFrozen()) {
10851089
clearByTypeCache();
10861090
}
1091+
1092+
// Cache a primary marker for the given bean.
1093+
if (beanDefinition.isPrimary()) {
1094+
this.primaryBeanNames.add(beanName);
1095+
}
10871096
}
10881097

10891098
@Override
@@ -1135,6 +1144,9 @@ protected void resetBeanDefinition(String beanName) {
11351144
// (e.g. the default StaticMessageSource in a StaticApplicationContext).
11361145
destroySingleton(beanName);
11371146

1147+
// Remove a cached primary marker for the given bean.
1148+
this.primaryBeanNames.remove(beanName);
1149+
11381150
// Notify all post-processors that the specified bean definition has been reset.
11391151
for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
11401152
processor.resetBeanDefinition(beanName);
@@ -1388,15 +1400,27 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str
13881400
}
13891401
}
13901402

1391-
// Step 3a: multiple beans as stream / array / standard collection / plain map
1403+
// Step 3: shortcut for declared dependency name matching target bean name
1404+
String dependencyName = descriptor.getDependencyName();
1405+
if (dependencyName != null && containsBean(dependencyName) &&
1406+
isTypeMatch(dependencyName, type) && isAutowireCandidate(dependencyName, descriptor) &&
1407+
!hasPrimaryConflict(dependencyName, type) && !isSelfReference(beanName, dependencyName)) {
1408+
if (autowiredBeanNames != null) {
1409+
autowiredBeanNames.add(dependencyName);
1410+
}
1411+
Object dependencyBean = getBean(dependencyName);
1412+
return resolveInstance(dependencyBean, descriptor, type, dependencyName);
1413+
}
1414+
1415+
// Step 4a: multiple beans as stream / array / standard collection / plain map
13921416
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
13931417
if (multipleBeans != null) {
13941418
return multipleBeans;
13951419
}
1396-
// Step 3b: direct bean matches, possibly direct beans of type Collection / Map
1420+
// Step 4b: direct bean matches, possibly direct beans of type Collection / Map
13971421
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
13981422
if (matchingBeans.isEmpty()) {
1399-
// Step 3c (fallback): custom Collection / Map declarations for collecting multiple beans
1423+
// Step 4c (fallback): custom Collection / Map declarations for collecting multiple beans
14001424
multipleBeans = resolveMultipleBeansFallback(descriptor, beanName, autowiredBeanNames, typeConverter);
14011425
if (multipleBeans != null) {
14021426
return multipleBeans;
@@ -1411,7 +1435,7 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str
14111435
String autowiredBeanName;
14121436
Object instanceCandidate;
14131437

1414-
// Step 4: determine single candidate
1438+
// Step 5: determine single candidate
14151439
if (matchingBeans.size() > 1) {
14161440
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
14171441
if (autowiredBeanName == null) {
@@ -1435,31 +1459,37 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str
14351459
instanceCandidate = entry.getValue();
14361460
}
14371461

1438-
// Step 5: validate single result
1462+
// Step 6: validate single result
14391463
if (autowiredBeanNames != null) {
14401464
autowiredBeanNames.add(autowiredBeanName);
14411465
}
14421466
if (instanceCandidate instanceof Class) {
14431467
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
14441468
}
1445-
Object result = instanceCandidate;
1446-
if (result instanceof NullBean) {
1447-
if (isRequired(descriptor)) {
1448-
// Raise exception if null encountered for required injection point
1449-
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
1450-
}
1451-
result = null;
1452-
}
1453-
if (!ClassUtils.isAssignableValue(type, result)) {
1454-
throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
1455-
}
1456-
return result;
1469+
return resolveInstance(instanceCandidate, descriptor, type, autowiredBeanName);
14571470
}
14581471
finally {
14591472
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
14601473
}
14611474
}
14621475

1476+
@Nullable
1477+
private Object resolveInstance(Object candidate, DependencyDescriptor descriptor, Class<?> type, String name) {
1478+
Object result = candidate;
1479+
if (result instanceof NullBean) {
1480+
// Raise exception if null encountered for required injection point
1481+
if (isRequired(descriptor)) {
1482+
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
1483+
}
1484+
result = null;
1485+
}
1486+
if (!ClassUtils.isAssignableValue(type, result)) {
1487+
throw new BeanNotOfRequiredTypeException(name, type, candidate.getClass());
1488+
}
1489+
return result;
1490+
1491+
}
1492+
14631493
@Nullable
14641494
private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
14651495
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
@@ -1712,20 +1742,27 @@ else if (containsSingleton(candidateName) || (descriptor instanceof StreamDepend
17121742
@Nullable
17131743
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
17141744
Class<?> requiredType = descriptor.getDependencyType();
1745+
// Step 1: check primary candidate
17151746
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
17161747
if (primaryCandidate != null) {
17171748
return primaryCandidate;
17181749
}
1750+
// Step 2: check bean name match
1751+
for (String candidateName : candidates.keySet()) {
1752+
if (matchesBeanName(candidateName, descriptor.getDependencyName())) {
1753+
return candidateName;
1754+
}
1755+
}
1756+
// Step 3: check highest priority candidate
17191757
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
17201758
if (priorityCandidate != null) {
17211759
return priorityCandidate;
17221760
}
1723-
// Fallback: pick directly registered dependency or qualified bean name match
1761+
// Step 4: pick directly registered dependency
17241762
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
17251763
String candidateName = entry.getKey();
17261764
Object beanInstance = entry.getValue();
1727-
if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
1728-
matchesBeanName(candidateName, descriptor.getDependencyName())) {
1765+
if (beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) {
17291766
return candidateName;
17301767
}
17311768
}
@@ -1866,6 +1903,21 @@ private boolean isSelfReference(@Nullable String beanName, @Nullable String cand
18661903
beanName.equals(getMergedLocalBeanDefinition(candidateName).getFactoryBeanName()))));
18671904
}
18681905

1906+
/**
1907+
* Determine whether there is a primary bean registered for the given dependency type,
1908+
* not matching the given bean name.
1909+
*/
1910+
@Nullable
1911+
private boolean hasPrimaryConflict(String beanName, Class<?> dependencyType) {
1912+
for (String candidate : this.primaryBeanNames) {
1913+
if (isTypeMatch(candidate, dependencyType) && !candidate.equals(beanName)) {
1914+
return true;
1915+
}
1916+
}
1917+
return (getParentBeanFactory() instanceof DefaultListableBeanFactory parent &&
1918+
parent.hasPrimaryConflict(beanName, dependencyType));
1919+
}
1920+
18691921
/**
18701922
* Raise a NoSuchBeanDefinitionException or BeanNotOfRequiredTypeException
18711923
* for an unresolvable dependency.

spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

-17
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818

1919
import java.io.Closeable;
2020
import java.io.Serializable;
21-
import java.lang.reflect.Constructor;
2221
import java.lang.reflect.Field;
23-
import java.lang.reflect.Method;
2422
import java.net.MalformedURLException;
2523
import java.text.NumberFormat;
2624
import java.text.ParseException;
@@ -81,7 +79,6 @@
8179
import org.springframework.core.DefaultParameterNameDiscoverer;
8280
import org.springframework.core.MethodParameter;
8381
import org.springframework.core.Ordered;
84-
import org.springframework.core.ParameterNameDiscoverer;
8582
import org.springframework.core.ResolvableType;
8683
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
8784
import org.springframework.core.annotation.Order;
@@ -121,20 +118,6 @@ class DefaultListableBeanFactoryTests {
121118

122119
private final DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
123120

124-
{
125-
// No parameter name discovery expected unless named arguments are used
126-
lbf.setParameterNameDiscoverer(new ParameterNameDiscoverer() {
127-
@Override
128-
public String[] getParameterNames(Method method) {
129-
throw new UnsupportedOperationException();
130-
}
131-
@Override
132-
public String[] getParameterNames(Constructor<?> ctor) {
133-
throw new UnsupportedOperationException();
134-
}
135-
});
136-
}
137-
138121

139122
@Test
140123
void unreferencedSingletonWasInstantiated() {

0 commit comments

Comments
 (0)