Skip to content

Commit 567547b

Browse files
committed
Skip shortcut resolution for non-standard dependency descriptors
Closes gh-32326 See gh-28122
1 parent 98efa1d commit 567547b

File tree

4 files changed

+109
-61
lines changed

4 files changed

+109
-61
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java

Lines changed: 20 additions & 1 deletion
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.
@@ -332,6 +332,10 @@ public DependencyDescriptor forFallbackMatch() {
332332
public boolean fallbackMatchAllowed() {
333333
return true;
334334
}
335+
@Override
336+
public boolean usesStandardBeanLookup() {
337+
return true;
338+
}
335339
};
336340
}
337341

@@ -385,6 +389,21 @@ public boolean supportsLazyResolution() {
385389
return true;
386390
}
387391

392+
/**
393+
* Determine whether this descriptor uses a standard bean lookup
394+
* in {@link #resolveCandidate(String, Class, BeanFactory)} and
395+
* therefore qualifies for factory-level shortcut resolution.
396+
* <p>By default, the {@code DependencyDescriptor} class itself
397+
* uses a standard bean lookup but subclasses may override this.
398+
* If a subclass overrides other methods but preserves a standard
399+
* bean lookup, it may override this method to return {@code true}.
400+
* @since 6.2
401+
* @see #resolveCandidate(String, Class, BeanFactory)
402+
*/
403+
public boolean usesStandardBeanLookup() {
404+
return (getClass() == DependencyDescriptor.class);
405+
}
406+
388407

389408
@Override
390409
public boolean equals(@Nullable Object other) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,11 @@ public Object resolveShortcut(BeanFactory beanFactory) {
14391439
String shortcut = this.shortcut;
14401440
return (shortcut != null ? beanFactory.getBean(shortcut, getDependencyType()) : null);
14411441
}
1442+
1443+
@Override
1444+
public boolean usesStandardBeanLookup() {
1445+
return true;
1446+
}
14421447
}
14431448

14441449

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

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,20 +1400,24 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str
14001400
}
14011401

14021402
// Step 3: shortcut for declared dependency name or qualifier-suggested name matching target bean name
1403-
String dependencyName = descriptor.getDependencyName();
1404-
if (dependencyName == null || !containsBean(dependencyName)) {
1405-
String suggestedName = getAutowireCandidateResolver().getSuggestedName(descriptor);
1406-
dependencyName = (suggestedName != null && containsBean(suggestedName) ? suggestedName : null);
1407-
}
1408-
if (dependencyName != null &&
1409-
isTypeMatch(dependencyName, type) && isAutowireCandidate(dependencyName, descriptor) &&
1410-
!isFallback(dependencyName) && !hasPrimaryConflict(dependencyName, type) &&
1411-
!isSelfReference(beanName, dependencyName)) {
1412-
if (autowiredBeanNames != null) {
1413-
autowiredBeanNames.add(dependencyName);
1403+
if (descriptor.usesStandardBeanLookup()) {
1404+
String dependencyName = descriptor.getDependencyName();
1405+
if (dependencyName == null || !containsBean(dependencyName)) {
1406+
String suggestedName = getAutowireCandidateResolver().getSuggestedName(descriptor);
1407+
dependencyName = (suggestedName != null && containsBean(suggestedName) ? suggestedName : null);
1408+
}
1409+
if (dependencyName != null) {
1410+
dependencyName = canonicalName(dependencyName); // dependency name can be alias of target name
1411+
if (isTypeMatch(dependencyName, type) && isAutowireCandidate(dependencyName, descriptor) &&
1412+
!isFallback(dependencyName) && !hasPrimaryConflict(dependencyName, type) &&
1413+
!isSelfReference(beanName, dependencyName)) {
1414+
if (autowiredBeanNames != null) {
1415+
autowiredBeanNames.add(dependencyName);
1416+
}
1417+
Object dependencyBean = getBean(dependencyName);
1418+
return resolveInstance(dependencyBean, descriptor, type, dependencyName);
1419+
}
14141420
}
1415-
Object dependencyBean = getBean(dependencyName);
1416-
return resolveInstance(dependencyBean, descriptor, type, dependencyName);
14171421
}
14181422

14191423
// Step 4a: multiple beans as stream / array / standard collection / plain map
@@ -2020,6 +2024,10 @@ public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFacto
20202024
return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, args) :
20212025
super.resolveCandidate(beanName, requiredType, beanFactory));
20222026
}
2027+
@Override
2028+
public boolean usesStandardBeanLookup() {
2029+
return ObjectUtils.isEmpty(args);
2030+
}
20232031
};
20242032
Object result = doResolveDependency(descriptorToUse, beanName, null, null);
20252033
return (result instanceof Optional<?> optional ? optional : Optional.ofNullable(result));
@@ -2101,6 +2109,11 @@ public NestedDependencyDescriptor(DependencyDescriptor original) {
21012109
super(original);
21022110
increaseNestingLevel();
21032111
}
2112+
2113+
@Override
2114+
public boolean usesStandardBeanLookup() {
2115+
return true;
2116+
}
21042117
}
21052118

21062119

@@ -2202,6 +2215,10 @@ public Object getIfAvailable() throws BeansException {
22022215
public boolean isRequired() {
22032216
return false;
22042217
}
2218+
@Override
2219+
public boolean usesStandardBeanLookup() {
2220+
return true;
2221+
}
22052222
};
22062223
return doResolveDependency(descriptorToUse, this.beanName, null, null);
22072224
}
@@ -2234,6 +2251,10 @@ public boolean isRequired() {
22342251
return false;
22352252
}
22362253
@Override
2254+
public boolean usesStandardBeanLookup() {
2255+
return true;
2256+
}
2257+
@Override
22372258
@Nullable
22382259
public Object resolveNotUnique(ResolvableType type, Map<String, Object> matchingBeans) {
22392260
return null;

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

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,16 +1234,17 @@ void constructorInjectionWithMap() {
12341234
RootBeanDefinition tb2 = new RootBeanDefinition(NullFactoryMethods.class);
12351235
tb2.setFactoryMethodName("createTestBean");
12361236
bf.registerBeanDefinition("testBean2", tb2);
1237+
bf.registerAlias("testBean2", "testBean");
12371238

12381239
MapConstructorInjectionBean bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
1239-
assertThat(bean.getTestBeanMap()).hasSize(1);
1240-
assertThat(bean.getTestBeanMap().get("testBean1")).isSameAs(tb1);
1241-
assertThat(bean.getTestBeanMap().get("testBean2")).isNull();
1240+
assertThat(bean.getTestBean()).hasSize(1);
1241+
assertThat(bean.getTestBean().get("testBean1")).isSameAs(tb1);
1242+
assertThat(bean.getTestBean().get("testBean2")).isNull();
12421243

12431244
bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
1244-
assertThat(bean.getTestBeanMap()).hasSize(1);
1245-
assertThat(bean.getTestBeanMap().get("testBean1")).isSameAs(tb1);
1246-
assertThat(bean.getTestBeanMap().get("testBean2")).isNull();
1245+
assertThat(bean.getTestBean()).hasSize(1);
1246+
assertThat(bean.getTestBean().get("testBean1")).isSameAs(tb1);
1247+
assertThat(bean.getTestBean().get("testBean2")).isNull();
12471248
}
12481249

12491250
@Test
@@ -1255,6 +1256,7 @@ void fieldInjectionWithMap() {
12551256
TestBean tb2 = new TestBean("tb2");
12561257
bf.registerSingleton("testBean1", tb1);
12571258
bf.registerSingleton("testBean2", tb2);
1259+
bf.registerAlias("testBean1", "testBean");
12581260

12591261
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
12601262
assertThat(bean.getTestBeanMap()).hasSize(2);
@@ -1339,9 +1341,9 @@ void constructorInjectionWithTypedMapAsBean() {
13391341
bf.registerSingleton("otherMap", new Properties());
13401342

13411343
MapConstructorInjectionBean bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
1342-
assertThat(bean.getTestBeanMap()).isSameAs(tbm);
1344+
assertThat(bean.getTestBean()).isSameAs(tbm);
13431345
bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
1344-
assertThat(bean.getTestBeanMap()).isSameAs(tbm);
1346+
assertThat(bean.getTestBean()).isSameAs(tbm);
13451347
}
13461348

13471349
@Test
@@ -1355,9 +1357,9 @@ void constructorInjectionWithPlainMapAsBean() {
13551357
bf.registerSingleton("otherMap", new HashMap<>());
13561358

13571359
MapConstructorInjectionBean bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
1358-
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
1360+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("myTestBeanMap"));
13591361
bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
1360-
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
1362+
assertThat(bean.getTestBean()).isSameAs(bf.getBean("myTestBeanMap"));
13611363
}
13621364

13631365
@Test
@@ -1578,32 +1580,33 @@ void objectFactorySerialization() throws Exception {
15781580
@Test
15791581
void objectProviderInjectionWithPrototype() {
15801582
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class));
1581-
RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class);
1582-
tbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1583-
bf.registerBeanDefinition("testBean", tbd);
1583+
RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class);
1584+
tb1.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1585+
bf.registerBeanDefinition("testBean1", tb1);
1586+
RootBeanDefinition tb2 = new RootBeanDefinition(TestBean.class);
1587+
tb2.setScope(BeanDefinition.SCOPE_PROTOTYPE);
1588+
tb2.setPrimary(true);
1589+
bf.registerBeanDefinition("testBean2", tb2);
1590+
bf.registerAlias("testBean2", "testBean");
15841591

15851592
ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class);
1586-
assertThat(bean.getTestBean()).isEqualTo(bf.getBean("testBean"));
1587-
assertThat(bean.getTestBean("myName")).isEqualTo(bf.getBean("testBean", "myName"));
1588-
assertThat(bean.getOptionalTestBean()).isEqualTo(bf.getBean("testBean"));
1589-
assertThat(bean.getOptionalTestBeanWithDefault()).isEqualTo(bf.getBean("testBean"));
1590-
assertThat(bean.consumeOptionalTestBean()).isEqualTo(bf.getBean("testBean"));
1591-
assertThat(bean.getUniqueTestBean()).isEqualTo(bf.getBean("testBean"));
1592-
assertThat(bean.getUniqueTestBeanWithDefault()).isEqualTo(bf.getBean("testBean"));
1593-
assertThat(bean.consumeUniqueTestBean()).isEqualTo(bf.getBean("testBean"));
1593+
assertThat(bean.getTestBean()).isEqualTo(bf.getBean("testBean2"));
1594+
assertThat(bean.getTestBean("myName")).isEqualTo(bf.getBean("testBean2", "myName"));
1595+
assertThat(bean.getOptionalTestBean()).isEqualTo(bf.getBean("testBean2"));
1596+
assertThat(bean.getOptionalTestBeanWithDefault()).isEqualTo(bf.getBean("testBean2"));
1597+
assertThat(bean.consumeOptionalTestBean()).isEqualTo(bf.getBean("testBean2"));
1598+
assertThat(bean.getUniqueTestBean()).isEqualTo(bf.getBean("testBean2"));
1599+
assertThat(bean.getUniqueTestBeanWithDefault()).isEqualTo(bf.getBean("testBean2"));
1600+
assertThat(bean.consumeUniqueTestBean()).isEqualTo(bf.getBean("testBean2"));
15941601

15951602
List<TestBean> testBeans = bean.iterateTestBeans();
1596-
assertThat(testBeans).hasSize(1);
1597-
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
1603+
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
15981604
testBeans = bean.forEachTestBeans();
1599-
assertThat(testBeans).hasSize(1);
1600-
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
1605+
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
16011606
testBeans = bean.streamTestBeans();
1602-
assertThat(testBeans).hasSize(1);
1603-
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
1607+
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
16041608
testBeans = bean.sortedTestBeans();
1605-
assertThat(testBeans).hasSize(1);
1606-
assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
1609+
assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
16071610
}
16081611

16091612
@Test
@@ -3078,15 +3081,15 @@ public static class MyTestBeanSet extends LinkedHashSet<TestBean> {
30783081

30793082
public static class MapConstructorInjectionBean {
30803083

3081-
private Map<String, TestBean> testBeanMap;
3084+
private Map<String, TestBean> testBean; // matches bean name but should not apply shortcut
30823085

30833086
@Autowired
3084-
public MapConstructorInjectionBean(Map<String, TestBean> testBeanMap) {
3085-
this.testBeanMap = testBeanMap;
3087+
public MapConstructorInjectionBean(Map<String, TestBean> testBean) {
3088+
this.testBean = testBean;
30863089
}
30873090

3088-
public Map<String, TestBean> getTestBeanMap() {
3089-
return this.testBeanMap;
3091+
public Map<String, TestBean> getTestBean() {
3092+
return this.testBean;
30903093
}
30913094
}
30923095

@@ -3247,64 +3250,64 @@ public TestBean getTestBean() {
32473250
public static class ObjectProviderInjectionBean {
32483251

32493252
@Autowired
3250-
private ObjectProvider<TestBean> testBeanProvider;
3253+
private ObjectProvider<TestBean> testBean; // matches bean name but should not apply shortcut
32513254

32523255
private TestBean consumedTestBean;
32533256

32543257
public TestBean getTestBean() {
3255-
return this.testBeanProvider.getObject();
3258+
return this.testBean.getObject();
32563259
}
32573260

32583261
public TestBean getTestBean(String name) {
3259-
return this.testBeanProvider.getObject(name);
3262+
return this.testBean.getObject(name);
32603263
}
32613264

32623265
public TestBean getOptionalTestBean() {
3263-
return this.testBeanProvider.getIfAvailable();
3266+
return this.testBean.getIfAvailable();
32643267
}
32653268

32663269
public TestBean getOptionalTestBeanWithDefault() {
3267-
return this.testBeanProvider.getIfAvailable(() -> new TestBean("default"));
3270+
return this.testBean.getIfAvailable(() -> new TestBean("default"));
32683271
}
32693272

32703273
public TestBean consumeOptionalTestBean() {
3271-
this.testBeanProvider.ifAvailable(tb -> consumedTestBean = tb);
3274+
this.testBean.ifAvailable(tb -> consumedTestBean = tb);
32723275
return consumedTestBean;
32733276
}
32743277

32753278
public TestBean getUniqueTestBean() {
3276-
return this.testBeanProvider.getIfUnique();
3279+
return this.testBean.getIfUnique();
32773280
}
32783281

32793282
public TestBean getUniqueTestBeanWithDefault() {
3280-
return this.testBeanProvider.getIfUnique(() -> new TestBean("default"));
3283+
return this.testBean.getIfUnique(() -> new TestBean("default"));
32813284
}
32823285

32833286
public TestBean consumeUniqueTestBean() {
3284-
this.testBeanProvider.ifUnique(tb -> consumedTestBean = tb);
3287+
this.testBean.ifUnique(tb -> consumedTestBean = tb);
32853288
return consumedTestBean;
32863289
}
32873290

32883291
public List<TestBean> iterateTestBeans() {
32893292
List<TestBean> resolved = new ArrayList<>();
3290-
for (TestBean tb : this.testBeanProvider) {
3293+
for (TestBean tb : this.testBean) {
32913294
resolved.add(tb);
32923295
}
32933296
return resolved;
32943297
}
32953298

32963299
public List<TestBean> forEachTestBeans() {
32973300
List<TestBean> resolved = new ArrayList<>();
3298-
this.testBeanProvider.forEach(resolved::add);
3301+
this.testBean.forEach(resolved::add);
32993302
return resolved;
33003303
}
33013304

33023305
public List<TestBean> streamTestBeans() {
3303-
return this.testBeanProvider.stream().toList();
3306+
return this.testBean.stream().toList();
33043307
}
33053308

33063309
public List<TestBean> sortedTestBeans() {
3307-
return this.testBeanProvider.orderedStream().toList();
3310+
return this.testBean.orderedStream().toList();
33083311
}
33093312
}
33103313

0 commit comments

Comments
 (0)