Skip to content

Commit c4fcdb6

Browse files
committed
DefaultListableBeanFactory does not trigger early candidate creation ahead of primary bean selection
Issue: SPR-14611
1 parent 405e74b commit c4fcdb6

File tree

4 files changed

+121
-88
lines changed

4 files changed

+121
-88
lines changed

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

Lines changed: 110 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -976,38 +976,47 @@ public <T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType) throws Bea
976976
return null;
977977
}
978978

979+
@SuppressWarnings("unchecked")
979980
private <T> NamedBeanHolder<T> resolveNamedBean(Class<T> requiredType, Object... args) throws BeansException {
980981
Assert.notNull(requiredType, "Required type must not be null");
981-
String[] beanNames = getBeanNamesForType(requiredType);
982+
String[] candidateNames = getBeanNamesForType(requiredType);
982983

983-
if (beanNames.length > 1) {
984+
if (candidateNames.length > 1) {
984985
ArrayList<String> autowireCandidates = new ArrayList<>();
985-
for (String beanName : beanNames) {
986+
for (String beanName : candidateNames) {
986987
if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) {
987988
autowireCandidates.add(beanName);
988989
}
989990
}
990991
if (!autowireCandidates.isEmpty()) {
991-
beanNames = autowireCandidates.toArray(new String[autowireCandidates.size()]);
992+
candidateNames = autowireCandidates.toArray(new String[autowireCandidates.size()]);
992993
}
993994
}
994995

995-
if (beanNames.length == 1) {
996-
String beanName = beanNames[0];
996+
if (candidateNames.length == 1) {
997+
String beanName = candidateNames[0];
997998
return new NamedBeanHolder<>(beanName, getBean(beanName, requiredType, args));
998999
}
999-
else if (beanNames.length > 1) {
1000-
Map<String, Object> candidates = new LinkedHashMap<>();
1001-
for (String beanName : beanNames) {
1002-
candidates.put(beanName, getBean(beanName, requiredType, args));
1000+
else if (candidateNames.length > 1) {
1001+
Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length);
1002+
for (String candidateName : candidateNames) {
1003+
if (containsSingleton(candidateName)) {
1004+
candidates.put(candidateName, getBean(candidateName, requiredType, args));
1005+
}
1006+
else {
1007+
candidates.put(candidateName, getType(candidateName));
1008+
}
10031009
}
1004-
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
1005-
if (primaryCandidate != null) {
1006-
return new NamedBeanHolder<>(primaryCandidate, getBean(primaryCandidate, requiredType, args));
1010+
String candidateName = determinePrimaryCandidate(candidates, requiredType);
1011+
if (candidateName == null) {
1012+
candidateName = determineHighestPriorityCandidate(candidates, requiredType);
10071013
}
1008-
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
1009-
if (priorityCandidate != null) {
1010-
return new NamedBeanHolder<>(priorityCandidate, getBean(priorityCandidate, requiredType, args));
1014+
if (candidateName != null) {
1015+
Object beanInstance = candidates.get(candidateName);
1016+
if (beanInstance instanceof Class) {
1017+
beanInstance = getBean(candidateName, requiredType, args);
1018+
}
1019+
return new NamedBeanHolder<>(candidateName, (T) beanInstance);
10111020
}
10121021
throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
10131022
}
@@ -1076,9 +1085,13 @@ public Object doResolveDependency(DependencyDescriptor descriptor, String beanNa
10761085
}
10771086
return null;
10781087
}
1088+
1089+
String autowiredBeanName;
1090+
Object instanceCandidate;
1091+
10791092
if (matchingBeans.size() > 1) {
1080-
String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
1081-
if (primaryBeanName == null) {
1093+
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
1094+
if (autowiredBeanName == null) {
10821095
if (descriptor.isRequired() || !indicatesMultipleBeans(type)) {
10831096
return descriptor.resolveNotUnique(type, matchingBeans);
10841097
}
@@ -1089,17 +1102,20 @@ public Object doResolveDependency(DependencyDescriptor descriptor, String beanNa
10891102
return null;
10901103
}
10911104
}
1092-
if (autowiredBeanNames != null) {
1093-
autowiredBeanNames.add(primaryBeanName);
1094-
}
1095-
return matchingBeans.get(primaryBeanName);
1105+
instanceCandidate = matchingBeans.get(autowiredBeanName);
10961106
}
1097-
// We have exactly one match.
1098-
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
1107+
else {
1108+
// We have exactly one match.
1109+
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
1110+
autowiredBeanName = entry.getKey();
1111+
instanceCandidate = entry.getValue();
1112+
}
1113+
10991114
if (autowiredBeanNames != null) {
1100-
autowiredBeanNames.add(entry.getKey());
1115+
autowiredBeanNames.add(autowiredBeanName);
11011116
}
1102-
return entry.getValue();
1117+
return (instanceCandidate instanceof Class ?
1118+
descriptor.resolveCandidate(autowiredBeanName, type, this) : instanceCandidate);
11031119
}
11041120
finally {
11051121
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
@@ -1112,9 +1128,8 @@ private Object resolveMultipleBeans(DependencyDescriptor descriptor, String bean
11121128
Class<?> type = descriptor.getDependencyType();
11131129
if (type.isArray()) {
11141130
Class<?> componentType = type.getComponentType();
1115-
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
1116-
targetDesc.increaseNestingLevel();
1117-
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, targetDesc);
1131+
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
1132+
new MultiElementDependencyDescriptor(descriptor));
11181133
if (matchingBeans.isEmpty()) {
11191134
return null;
11201135
}
@@ -1133,9 +1148,8 @@ else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
11331148
if (elementType == null) {
11341149
return null;
11351150
}
1136-
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
1137-
targetDesc.increaseNestingLevel();
1138-
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, targetDesc);
1151+
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
1152+
new MultiElementDependencyDescriptor(descriptor));
11391153
if (matchingBeans.isEmpty()) {
11401154
return null;
11411155
}
@@ -1158,9 +1172,8 @@ else if (Map.class.isAssignableFrom(type) && type.isInterface()) {
11581172
if (valueType == null) {
11591173
return null;
11601174
}
1161-
DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor);
1162-
targetDesc.increaseNestingLevel();
1163-
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, targetDesc);
1175+
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
1176+
new MultiElementDependencyDescriptor(descriptor));
11641177
if (matchingBeans.isEmpty()) {
11651178
return null;
11661179
}
@@ -1229,79 +1242,94 @@ protected Map<String, Object> findAutowireCandidates(
12291242
}
12301243
for (String candidateName : candidateNames) {
12311244
if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
1232-
result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
1245+
addCandidateEntry(result, candidateName, descriptor, requiredType);
12331246
}
12341247
}
12351248
if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) {
12361249
// Consider fallback matches if the first pass failed to find anything...
12371250
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
12381251
for (String candidateName : candidateNames) {
12391252
if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) {
1240-
result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
1253+
addCandidateEntry(result, candidateName, descriptor, requiredType);
12411254
}
12421255
}
12431256
if (result.isEmpty()) {
12441257
// Consider self references before as a final pass
12451258
for (String candidateName : candidateNames) {
12461259
if (isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) {
1247-
result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
1260+
addCandidateEntry(result, candidateName, descriptor, requiredType);
12481261
}
12491262
}
12501263
}
12511264
}
12521265
return result;
12531266
}
12541267

1268+
/**
1269+
* Add an entry to the candidate map: a bean instance if available or just the resolved
1270+
* type, preventing early bean initialization ahead of primary candidate selection.
1271+
*/
1272+
private void addCandidateEntry(Map<String, Object> candidates, String candidateName,
1273+
DependencyDescriptor descriptor, Class<?> requiredType) {
1274+
1275+
if (descriptor instanceof MultiElementDependencyDescriptor || containsSingleton(candidateName)) {
1276+
candidates.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this));
1277+
}
1278+
else {
1279+
candidates.put(candidateName, getType(candidateName));
1280+
}
1281+
}
1282+
12551283
/**
12561284
* Determine the autowire candidate in the given set of beans.
12571285
* <p>Looks for {@code @Primary} and {@code @Priority} (in that order).
1258-
* @param candidateBeans a Map of candidate names and candidate instances
1286+
* @param candidates a Map of candidate names and candidate instances
12591287
* that match the required type, as returned by {@link #findAutowireCandidates}
12601288
* @param descriptor the target dependency to match against
12611289
* @return the name of the autowire candidate, or {@code null} if none found
12621290
*/
1263-
protected String determineAutowireCandidate(Map<String, Object> candidateBeans, DependencyDescriptor descriptor) {
1291+
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
12641292
Class<?> requiredType = descriptor.getDependencyType();
1265-
String primaryCandidate = determinePrimaryCandidate(candidateBeans, requiredType);
1293+
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
12661294
if (primaryCandidate != null) {
12671295
return primaryCandidate;
12681296
}
1269-
String priorityCandidate = determineHighestPriorityCandidate(candidateBeans, requiredType);
1297+
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
12701298
if (priorityCandidate != null) {
12711299
return priorityCandidate;
12721300
}
12731301
// Fallback
1274-
for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
1275-
String candidateBeanName = entry.getKey();
1302+
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
1303+
String candidateName = entry.getKey();
12761304
Object beanInstance = entry.getValue();
12771305
if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
1278-
matchesBeanName(candidateBeanName, descriptor.getDependencyName())) {
1279-
return candidateBeanName;
1306+
matchesBeanName(candidateName, descriptor.getDependencyName())) {
1307+
return candidateName;
12801308
}
12811309
}
12821310
return null;
12831311
}
12841312

12851313
/**
12861314
* Determine the primary candidate in the given set of beans.
1287-
* @param candidateBeans a Map of candidate names and candidate instances
1288-
* that match the required type
1315+
* @param candidates a Map of candidate names and candidate instances
1316+
* (or candidate classes if not created yet) that match the required type
12891317
* @param requiredType the target dependency type to match against
12901318
* @return the name of the primary candidate, or {@code null} if none found
12911319
* @see #isPrimary(String, Object)
12921320
*/
1293-
protected String determinePrimaryCandidate(Map<String, Object> candidateBeans, Class<?> requiredType) {
1321+
protected String determinePrimaryCandidate(Map<String, Object> candidates, Class<?> requiredType) {
12941322
String primaryBeanName = null;
1295-
for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
1323+
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
12961324
String candidateBeanName = entry.getKey();
12971325
Object beanInstance = entry.getValue();
12981326
if (isPrimary(candidateBeanName, beanInstance)) {
12991327
if (primaryBeanName != null) {
13001328
boolean candidateLocal = containsBeanDefinition(candidateBeanName);
13011329
boolean primaryLocal = containsBeanDefinition(primaryBeanName);
13021330
if (candidateLocal && primaryLocal) {
1303-
throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(),
1304-
"more than one 'primary' bean found among candidates: " + candidateBeans.keySet());
1331+
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
1332+
"more than one 'primary' bean found among candidates: " + candidates.keySet());
13051333
}
13061334
else if (candidateLocal) {
13071335
primaryBeanName = candidateBeanName;
@@ -1316,29 +1344,30 @@ else if (candidateLocal) {
13161344
}
13171345

13181346
/**
1319-
* Determine the candidate with the highest priority in the given set of beans. As
1320-
* defined by the {@link org.springframework.core.Ordered} interface, the lowest
1321-
* value has the highest priority.
1322-
* @param candidateBeans a Map of candidate names and candidate instances
1323-
* that match the required type
1347+
* Determine the candidate with the highest priority in the given set of beans.
1348+
* <p>Based on {@code @javax.annotation.Priority}. As defined by the related
1349+
* {@link org.springframework.core.Ordered} interface, the lowest value has
1350+
* the highest priority.
1351+
* @param candidates a Map of candidate names and candidate instances
1352+
* (or candidate classes if not created yet) that match the required type
13241353
* @param requiredType the target dependency type to match against
13251354
* @return the name of the candidate with the highest priority,
13261355
* or {@code null} if none found
13271356
* @see #getPriority(Object)
13281357
*/
1329-
protected String determineHighestPriorityCandidate(Map<String, Object> candidateBeans, Class<?> requiredType) {
1358+
protected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) {
13301359
String highestPriorityBeanName = null;
13311360
Integer highestPriority = null;
1332-
for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
1361+
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
13331362
String candidateBeanName = entry.getKey();
13341363
Object beanInstance = entry.getValue();
13351364
Integer candidatePriority = getPriority(beanInstance);
13361365
if (candidatePriority != null) {
13371366
if (highestPriorityBeanName != null) {
13381367
if (candidatePriority.equals(highestPriority)) {
1339-
throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(),
1340-
"Multiple beans found with the same priority ('" + highestPriority + "') " +
1341-
"among candidates: " + candidateBeans.keySet());
1368+
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
1369+
"Multiple beans found with the same priority ('" + highestPriority +
1370+
"') among candidates: " + candidates.keySet());
13421371
}
13431372
else if (candidatePriority < highestPriority) {
13441373
highestPriorityBeanName = candidateBeanName;
@@ -1453,7 +1482,7 @@ private void checkBeanNotOfRequiredType(Class<?> type, DependencyDescriptor desc
14531482
* Create an {@link Optional} wrapper for the specified dependency.
14541483
*/
14551484
private Optional<?> createOptionalDependency(DependencyDescriptor descriptor, String beanName, final Object... args) {
1456-
DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) {
1485+
DependencyDescriptor descriptorToUse = new NestedDependencyDescriptor(descriptor) {
14571486
@Override
14581487
public boolean isRequired() {
14591488
return false;
@@ -1464,7 +1493,6 @@ public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFacto
14641493
super.resolveCandidate(beanName, requiredType, beanFactory));
14651494
}
14661495
};
1467-
descriptorToUse.increaseNestingLevel();
14681496
return Optional.ofNullable(doResolveDependency(descriptorToUse, beanName, null, null));
14691497
}
14701498

@@ -1543,8 +1571,7 @@ private class DependencyObjectProvider implements ObjectProvider<Object>, Serial
15431571
private final String beanName;
15441572

15451573
public DependencyObjectProvider(DependencyDescriptor descriptor, String beanName) {
1546-
this.descriptor = new DependencyDescriptor(descriptor);
1547-
this.descriptor.increaseNestingLevel();
1574+
this.descriptor = new NestedDependencyDescriptor(descriptor);
15481575
this.optional = (this.descriptor.getDependencyType() == Optional.class);
15491576
this.beanName = beanName;
15501577
}
@@ -1684,4 +1711,21 @@ private RootBeanDefinition getRootBeanDefinition(String beanName) {
16841711
}
16851712
}
16861713

1714+
1715+
private static class NestedDependencyDescriptor extends DependencyDescriptor {
1716+
1717+
public NestedDependencyDescriptor(DependencyDescriptor original) {
1718+
super(original);
1719+
increaseNestingLevel();
1720+
}
1721+
}
1722+
1723+
1724+
private static class MultiElementDependencyDescriptor extends NestedDependencyDescriptor {
1725+
1726+
public MultiElementDependencyDescriptor(DependencyDescriptor original) {
1727+
super(original);
1728+
}
1729+
}
1730+
16871731
}

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

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,12 +1435,14 @@ public void testGetBeanByTypeWithAmbiguity() {
14351435
public void testGetBeanByTypeWithPrimary() throws Exception {
14361436
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
14371437
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
1438+
bd1.setLazyInit(true);
14381439
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
14391440
bd2.setPrimary(true);
14401441
lbf.registerBeanDefinition("bd1", bd1);
14411442
lbf.registerBeanDefinition("bd2", bd2);
14421443
TestBean bean = lbf.getBean(TestBean.class);
14431444
assertThat(bean.getBeanName(), equalTo("bd2"));
1445+
assertFalse(lbf.containsSingleton("bd1"));
14441446
}
14451447

14461448
@Test
@@ -1760,24 +1762,6 @@ public void testAutowireBeanByTypeWithTwoMatches() {
17601762
}
17611763
}
17621764

1763-
@Test
1764-
public void testAutowireBeanByTypeWithTwoMatchesAndParameterNameDiscovery() {
1765-
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
1766-
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
1767-
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
1768-
lbf.registerBeanDefinition("test", bd);
1769-
lbf.registerBeanDefinition("spouse", bd2);
1770-
try {
1771-
lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
1772-
fail("Should have thrown UnsatisfiedDependencyException");
1773-
}
1774-
catch (UnsatisfiedDependencyException ex) {
1775-
// expected
1776-
assertTrue(ex.getMessage().contains("test"));
1777-
assertTrue(ex.getMessage().contains("spouse"));
1778-
}
1779-
}
1780-
17811765
@Test
17821766
public void testAutowireBeanByTypeWithDependencyCheck() {
17831767
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();

0 commit comments

Comments
 (0)