Skip to content

Commit bce1445

Browse files
committed
Accept fallback match for bean name or method-level qualifier as well
Closes gh-35690
1 parent 285182b commit bce1445

File tree

3 files changed

+182
-89
lines changed

3 files changed

+182
-89
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,6 @@ public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDesc
180180
* {@code true} if a qualifier has been found and matched,
181181
* {@code null} if no qualifier has been found at all
182182
*/
183-
184183
@Nullable
185184
protected Boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
186185
boolean qualifierFound = false;
@@ -375,6 +374,14 @@ public boolean hasQualifier(DependencyDescriptor descriptor) {
375374
return true;
376375
}
377376
}
377+
MethodParameter methodParam = descriptor.getMethodParameter();
378+
if (methodParam != null) {
379+
for (Annotation annotation : methodParam.getMethodAnnotations()) {
380+
if (isQualifier(annotation.annotationType())) {
381+
return true;
382+
}
383+
}
384+
}
378385
return false;
379386
}
380387

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,8 +1653,8 @@ else if (descriptor.supportsLazyResolution()) {
16531653
return doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
16541654
}
16551655

1656+
@SuppressWarnings("NullAway") // Dataflow analysis limitation
16561657
@Nullable
1657-
@SuppressWarnings("NullAway")
16581658
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
16591659
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
16601660

@@ -1991,7 +1991,8 @@ protected Map<String, Object> findAutowireCandidates(
19911991
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
19921992
for (String candidate : candidateNames) {
19931993
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
1994-
(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
1994+
(!multiple || matchesBeanName(candidate, descriptor.getDependencyName()) ||
1995+
getAutowireCandidateResolver().hasQualifier(descriptor))) {
19951996
addCandidateEntry(result, candidate, descriptor, requiredType);
19961997
}
19971998
}
@@ -2262,12 +2263,12 @@ private String determineDefaultCandidate(Map<String, Object> candidates) {
22622263
}
22632264

22642265
/**
2265-
* Determine whether the given candidate name matches the bean name or the aliases
2266+
* Determine whether the given dependency name matches the bean name or the aliases
22662267
* stored in this bean definition.
22672268
*/
2268-
protected boolean matchesBeanName(String beanName, @Nullable String candidateName) {
2269-
return (candidateName != null &&
2270-
(candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName)));
2269+
protected boolean matchesBeanName(String beanName, @Nullable String dependencyName) {
2270+
return (dependencyName != null &&
2271+
(dependencyName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), dependencyName)));
22712272
}
22722273

22732274
/**

spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java

Lines changed: 167 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,88 +1275,6 @@ void constructorInjectionWithMap() {
12751275
assertThat(bean.getTestBean().get("testBean2")).isNull();
12761276
}
12771277

1278-
@Test
1279-
void fieldInjectionWithMap() {
1280-
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
1281-
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1282-
bf.registerBeanDefinition("annotatedBean", bd);
1283-
TestBean tb1 = new TestBean("tb1");
1284-
TestBean tb2 = new TestBean("tb2");
1285-
bf.registerSingleton("testBean1", tb1);
1286-
bf.registerSingleton("testBean2", tb2);
1287-
bf.registerAlias("testBean1", "testBean");
1288-
1289-
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
1290-
assertThat(bean.getTestBeanMap()).hasSize(2);
1291-
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
1292-
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
1293-
assertThat(bean.getTestBeanMap()).containsValue(tb1);
1294-
assertThat(bean.getTestBeanMap()).containsValue(tb2);
1295-
1296-
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
1297-
assertThat(bean.getTestBeanMap()).hasSize(2);
1298-
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
1299-
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
1300-
assertThat(bean.getTestBeanMap()).containsValue(tb1);
1301-
assertThat(bean.getTestBeanMap()).containsValue(tb2);
1302-
}
1303-
1304-
@Test
1305-
void methodInjectionWithMap() {
1306-
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
1307-
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1308-
bf.registerBeanDefinition("annotatedBean", bd);
1309-
TestBean tb = new TestBean();
1310-
bf.registerSingleton("testBean", tb);
1311-
1312-
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
1313-
assertThat(bean.getTestBeanMap()).hasSize(1);
1314-
assertThat(bean.getTestBeanMap()).containsKey("testBean");
1315-
assertThat(bean.getTestBeanMap()).containsValue(tb);
1316-
assertThat(bean.getTestBean()).isSameAs(tb);
1317-
1318-
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
1319-
assertThat(bean.getTestBeanMap()).hasSize(1);
1320-
assertThat(bean.getTestBeanMap()).containsKey("testBean");
1321-
assertThat(bean.getTestBeanMap()).containsValue(tb);
1322-
assertThat(bean.getTestBean()).isSameAs(tb);
1323-
}
1324-
1325-
@Test
1326-
void methodInjectionWithMapAndMultipleMatches() {
1327-
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
1328-
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
1329-
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
1330-
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
1331-
.isThrownBy(() -> bf.getBean("annotatedBean"))
1332-
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
1333-
}
1334-
1335-
@Test
1336-
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
1337-
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
1338-
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
1339-
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
1340-
rbd2.setAutowireCandidate(false);
1341-
bf.registerBeanDefinition("testBean2", rbd2);
1342-
1343-
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
1344-
TestBean tb = bf.getBean("testBean1", TestBean.class);
1345-
assertThat(bean.getTestBeanMap()).hasSize(1);
1346-
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
1347-
assertThat(bean.getTestBeanMap()).containsValue(tb);
1348-
assertThat(bean.getTestBean()).isSameAs(tb);
1349-
}
1350-
1351-
@Test
1352-
void methodInjectionWithMapAndNoMatches() {
1353-
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
1354-
1355-
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
1356-
assertThat(bean.getTestBeanMap()).isNull();
1357-
assertThat(bean.getTestBean()).isNull();
1358-
}
1359-
13601278
@Test
13611279
void constructorInjectionWithTypedMapAsBean() {
13621280
RootBeanDefinition bd = new RootBeanDefinition(MapConstructorInjectionBean.class);
@@ -1409,6 +1327,19 @@ void constructorInjectionWithCustomMapAsBean() {
14091327

14101328
@Test
14111329
void constructorInjectionWithPlainHashMapAsBean() {
1330+
RootBeanDefinition bd = new RootBeanDefinition(NamedMapConstructorInjectionBean.class);
1331+
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1332+
bf.registerBeanDefinition("annotatedBean", bd);
1333+
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
1334+
1335+
NamedMapConstructorInjectionBean bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
1336+
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
1337+
bean = bf.getBean("annotatedBean", NamedMapConstructorInjectionBean.class);
1338+
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
1339+
}
1340+
1341+
@Test
1342+
void constructorInjectionWithQualifiedPlainHashMapAsBean() {
14121343
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapConstructorInjectionBean.class);
14131344
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
14141345
bf.registerBeanDefinition("annotatedBean", bd);
@@ -1497,6 +1428,114 @@ void constructorInjectionWithSortedSetFallback() {
14971428
assertThat(bean.getTestBeanSet()).contains(tb1, tb2);
14981429
}
14991430

1431+
@Test
1432+
void fieldInjectionWithMap() {
1433+
RootBeanDefinition bd = new RootBeanDefinition(MapFieldInjectionBean.class);
1434+
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1435+
bf.registerBeanDefinition("annotatedBean", bd);
1436+
TestBean tb1 = new TestBean("tb1");
1437+
TestBean tb2 = new TestBean("tb2");
1438+
bf.registerSingleton("testBean1", tb1);
1439+
bf.registerSingleton("testBean2", tb2);
1440+
bf.registerAlias("testBean1", "testBean");
1441+
1442+
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
1443+
assertThat(bean.getTestBeanMap()).hasSize(2);
1444+
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
1445+
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
1446+
assertThat(bean.getTestBeanMap()).containsValue(tb1);
1447+
assertThat(bean.getTestBeanMap()).containsValue(tb2);
1448+
1449+
bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
1450+
assertThat(bean.getTestBeanMap()).hasSize(2);
1451+
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
1452+
assertThat(bean.getTestBeanMap()).containsKey("testBean2");
1453+
assertThat(bean.getTestBeanMap()).containsValue(tb1);
1454+
assertThat(bean.getTestBeanMap()).containsValue(tb2);
1455+
}
1456+
1457+
@Test
1458+
void methodInjectionWithMap() {
1459+
RootBeanDefinition bd = new RootBeanDefinition(MapMethodInjectionBean.class);
1460+
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1461+
bf.registerBeanDefinition("annotatedBean", bd);
1462+
TestBean tb = new TestBean();
1463+
bf.registerSingleton("testBean", tb);
1464+
1465+
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
1466+
assertThat(bean.getTestBeanMap()).hasSize(1);
1467+
assertThat(bean.getTestBeanMap()).containsKey("testBean");
1468+
assertThat(bean.getTestBeanMap()).containsValue(tb);
1469+
assertThat(bean.getTestBean()).isSameAs(tb);
1470+
1471+
bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
1472+
assertThat(bean.getTestBeanMap()).hasSize(1);
1473+
assertThat(bean.getTestBeanMap()).containsKey("testBean");
1474+
assertThat(bean.getTestBeanMap()).containsValue(tb);
1475+
assertThat(bean.getTestBean()).isSameAs(tb);
1476+
}
1477+
1478+
@Test
1479+
void methodInjectionWithMapAndMultipleMatches() {
1480+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
1481+
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
1482+
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
1483+
assertThatExceptionOfType(UnsatisfiedDependencyException.class).as("should have failed, more than one bean of type")
1484+
.isThrownBy(() -> bf.getBean("annotatedBean"))
1485+
.satisfies(methodParameterDeclaredOn(MapMethodInjectionBean.class));
1486+
}
1487+
1488+
@Test
1489+
void methodInjectionWithMapAndMultipleMatchesButOnlyOneAutowireCandidate() {
1490+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
1491+
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
1492+
RootBeanDefinition rbd2 = new RootBeanDefinition(TestBean.class);
1493+
rbd2.setAutowireCandidate(false);
1494+
bf.registerBeanDefinition("testBean2", rbd2);
1495+
1496+
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
1497+
TestBean tb = bf.getBean("testBean1", TestBean.class);
1498+
assertThat(bean.getTestBeanMap()).hasSize(1);
1499+
assertThat(bean.getTestBeanMap()).containsKey("testBean1");
1500+
assertThat(bean.getTestBeanMap()).containsValue(tb);
1501+
assertThat(bean.getTestBean()).isSameAs(tb);
1502+
}
1503+
1504+
@Test
1505+
void methodInjectionWithMapAndNoMatches() {
1506+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(MapMethodInjectionBean.class));
1507+
1508+
MapMethodInjectionBean bean = bf.getBean("annotatedBean", MapMethodInjectionBean.class);
1509+
assertThat(bean.getTestBeanMap()).isNull();
1510+
assertThat(bean.getTestBean()).isNull();
1511+
}
1512+
1513+
@Test
1514+
void methodInjectionWithPlainHashMapAsBean() {
1515+
RootBeanDefinition bd = new RootBeanDefinition(NamedMapMethodInjectionBean.class);
1516+
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1517+
bf.registerBeanDefinition("annotatedBean", bd);
1518+
bf.registerBeanDefinition("testBeanMap", new RootBeanDefinition(HashMap.class));
1519+
1520+
NamedMapMethodInjectionBean bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
1521+
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
1522+
bean = bf.getBean("annotatedBean", NamedMapMethodInjectionBean.class);
1523+
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("testBeanMap"));
1524+
}
1525+
1526+
@Test
1527+
void methodInjectionWithQualifiedPlainHashMapAsBean() {
1528+
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapMethodInjectionBean.class);
1529+
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1530+
bf.registerBeanDefinition("annotatedBean", bd);
1531+
bf.registerBeanDefinition("myTestBeanMap", new RootBeanDefinition(HashMap.class));
1532+
1533+
QualifiedMapMethodInjectionBean bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
1534+
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
1535+
bean = bf.getBean("annotatedBean", QualifiedMapMethodInjectionBean.class);
1536+
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
1537+
}
1538+
15001539
@Test
15011540
void selfReference() {
15021541
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class));
@@ -3258,6 +3297,21 @@ public SortedMap<String, TestBean> getTestBeanMap() {
32583297
}
32593298

32603299

3300+
public static class NamedMapConstructorInjectionBean {
3301+
3302+
private Map<String, TestBean> testBeanMap;
3303+
3304+
@Autowired
3305+
public NamedMapConstructorInjectionBean(Map<String, TestBean> testBeanMap) {
3306+
this.testBeanMap = testBeanMap;
3307+
}
3308+
3309+
public Map<String, TestBean> getTestBeanMap() {
3310+
return this.testBeanMap;
3311+
}
3312+
}
3313+
3314+
32613315
public static class QualifiedMapConstructorInjectionBean {
32623316

32633317
private Map<String, TestBean> testBeanMap;
@@ -3357,6 +3411,37 @@ public Map<String, TestBean> getTestBeanMap() {
33573411
}
33583412

33593413

3414+
public static class NamedMapMethodInjectionBean {
3415+
3416+
private Map<String, TestBean> testBeanMap;
3417+
3418+
@Autowired
3419+
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
3420+
this.testBeanMap = testBeanMap;
3421+
}
3422+
3423+
public Map<String, TestBean> getTestBeanMap() {
3424+
return this.testBeanMap;
3425+
}
3426+
}
3427+
3428+
3429+
public static class QualifiedMapMethodInjectionBean {
3430+
3431+
private Map<String, TestBean> testBeanMap;
3432+
3433+
@Autowired
3434+
@Qualifier("myTestBeanMap")
3435+
public void setTestBeanMap(Map<String, TestBean> testBeanMap) {
3436+
this.testBeanMap = testBeanMap;
3437+
}
3438+
3439+
public Map<String, TestBean> getTestBeanMap() {
3440+
return this.testBeanMap;
3441+
}
3442+
}
3443+
3444+
33603445
@SuppressWarnings("serial")
33613446
public static class ObjectFactoryFieldInjectionBean implements Serializable {
33623447

0 commit comments

Comments
 (0)