Skip to content

Commit e230ea5

Browse files
committed
Consistently resolve unique default candidate bean
Closes gh-34432
1 parent 94eb600 commit e230ea5

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

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

+20
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.beans.BeanMetadataElement;
3636
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3737
import org.springframework.beans.factory.ObjectFactory;
38+
import org.springframework.beans.factory.config.BeanDefinition;
3839
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
3940
import org.springframework.beans.factory.config.TypedStringValue;
4041
import org.springframework.lang.Nullable;
@@ -279,6 +280,25 @@ public static boolean isAutowireCandidate(ConfigurableBeanFactory beanFactory, S
279280
}
280281
}
281282

283+
/**
284+
* Check the default-candidate status for the specified bean.
285+
* @param beanFactory the bean factory
286+
* @param beanName the name of the bean to check
287+
* @return whether the specified bean qualifies as a default candidate
288+
* @since 6.2.4
289+
* @see AbstractBeanDefinition#isDefaultCandidate()
290+
*/
291+
public static boolean isDefaultCandidate(ConfigurableBeanFactory beanFactory, String beanName) {
292+
try {
293+
BeanDefinition mbd = beanFactory.getMergedBeanDefinition(beanName);
294+
return (!(mbd instanceof AbstractBeanDefinition abd) || abd.isDefaultCandidate());
295+
}
296+
catch (NoSuchBeanDefinitionException ex) {
297+
// A manually registered singleton instance not backed by a BeanDefinition.
298+
return true;
299+
}
300+
}
301+
282302

283303
/**
284304
* Reflective {@link InvocationHandler} for lazy access to the current target object.

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

+31-1
Original file line numberDiff line numberDiff line change
@@ -1497,6 +1497,9 @@ else if (candidateNames.length > 1) {
14971497
if (candidateName == null) {
14981498
candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass());
14991499
}
1500+
if (candidateName == null) {
1501+
candidateName = determineDefaultCandidate(candidates);
1502+
}
15001503
if (candidateName != null) {
15011504
Object beanInstance = candidates.get(candidateName);
15021505
if (beanInstance == null) {
@@ -1967,7 +1970,12 @@ protected String determineAutowireCandidate(Map<String, Object> candidates, Depe
19671970
if (priorityCandidate != null) {
19681971
return priorityCandidate;
19691972
}
1970-
// Step 4: pick directly registered dependency
1973+
// Step 4: pick unique default-candidate
1974+
String defaultCandidate = determineDefaultCandidate(candidates);
1975+
if (defaultCandidate != null) {
1976+
return defaultCandidate;
1977+
}
1978+
// Step 5: pick directly registered dependency
19711979
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
19721980
String candidateName = entry.getKey();
19731981
Object beanInstance = entry.getValue();
@@ -2128,6 +2136,28 @@ protected Integer getPriority(Object beanInstance) {
21282136
return null;
21292137
}
21302138

2139+
/**
2140+
* Return a unique "default-candidate" among remaining non-default candidates.
2141+
* @param candidates a Map of candidate names and candidate instances
2142+
* (or candidate classes if not created yet) that match the required type
2143+
* @return the name of the default candidate, or {@code null} if none found
2144+
* @since 6.2.4
2145+
* @see AbstractBeanDefinition#isDefaultCandidate()
2146+
*/
2147+
@Nullable
2148+
private String determineDefaultCandidate(Map<String, Object> candidates) {
2149+
String defaultBeanName = null;
2150+
for (String candidateBeanName : candidates.keySet()) {
2151+
if (AutowireUtils.isDefaultCandidate(this, candidateBeanName)) {
2152+
if (defaultBeanName != null) {
2153+
return null;
2154+
}
2155+
defaultBeanName = candidateBeanName;
2156+
}
2157+
}
2158+
return defaultBeanName;
2159+
}
2160+
21312161
/**
21322162
* Determine whether the given candidate name matches the bean name or the aliases
21332163
* stored in this bean definition.

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

+28
Original file line numberDiff line numberDiff line change
@@ -1658,12 +1658,40 @@ void getBeanByTypeWithPrimary() {
16581658
bd2.setPrimary(true);
16591659
lbf.registerBeanDefinition("bd1", bd1);
16601660
lbf.registerBeanDefinition("bd2", bd2);
1661+
lbf.registerSingleton("bd3", new TestBean());
16611662

16621663
TestBean bean = lbf.getBean(TestBean.class);
16631664
assertThat(bean.getBeanName()).isEqualTo("bd2");
16641665
assertThat(lbf.containsSingleton("bd1")).isFalse();
16651666
}
16661667

1668+
@Test
1669+
void getBeanByTypeWithUniqueNonDefaultDefinition() {
1670+
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
1671+
bd1.setDefaultCandidate(false);
1672+
bd1.setLazyInit(true);
1673+
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
1674+
lbf.registerBeanDefinition("bd1", bd1);
1675+
lbf.registerBeanDefinition("bd2", bd2);
1676+
1677+
TestBean bean = lbf.getBean(TestBean.class);
1678+
assertThat(bean.getBeanName()).isEqualTo("bd2");
1679+
assertThat(lbf.containsSingleton("bd1")).isFalse();
1680+
}
1681+
1682+
@Test
1683+
void getBeanByTypeWithUniqueNonDefaultSingleton() {
1684+
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
1685+
bd1.setDefaultCandidate(false);
1686+
bd1.setLazyInit(true);
1687+
lbf.registerBeanDefinition("bd1", bd1);
1688+
lbf.registerSingleton("bd2", new TestBean());
1689+
1690+
TestBean bean = lbf.getBean(TestBean.class);
1691+
assertThat(bean.getBeanName()).isNull();
1692+
assertThat(lbf.containsSingleton("bd1")).isFalse();
1693+
}
1694+
16671695
@Test
16681696
@SuppressWarnings("rawtypes")
16691697
void getFactoryBeanByTypeWithPrimary() {

0 commit comments

Comments
 (0)