diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java
index 499628a113..1fd4b9927b 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -332,6 +332,10 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
public boolean fallbackMatchAllowed() {
return true;
}
+ @Override
+ public boolean usesStandardBeanLookup() {
+ return true;
+ }
};
}
@@ -385,6 +389,21 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
return true;
}
+ /**
+ * Determine whether this descriptor uses a standard bean lookup
+ * in {@link #resolveCandidate(String, Class, BeanFactory)} and
+ * therefore qualifies for factory-level shortcut resolution.
+ *
By default, the {@code DependencyDescriptor} class itself
+ * uses a standard bean lookup but subclasses may override this.
+ * If a subclass overrides other methods but preserves a standard
+ * bean lookup, it may override this method to return {@code true}.
+ * @since 6.2
+ * @see #resolveCandidate(String, Class, BeanFactory)
+ */
+ public boolean usesStandardBeanLookup() {
+ return (getClass() == DependencyDescriptor.class);
+ }
+
@Override
public boolean equals(@Nullable Object other) {
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
index f1ded578a7..80475d1004 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
@@ -1439,6 +1439,11 @@ class ConstructorResolver {
String shortcut = this.shortcut;
return (shortcut != null ? beanFactory.getBean(shortcut, getDependencyType()) : null);
}
+
+ @Override
+ public boolean usesStandardBeanLookup() {
+ return true;
+ }
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
index 3efb785f6d..51ee1a8484 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
@@ -1400,20 +1400,24 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
// Step 3: shortcut for declared dependency name or qualifier-suggested name matching target bean name
- String dependencyName = descriptor.getDependencyName();
- if (dependencyName == null || !containsBean(dependencyName)) {
- String suggestedName = getAutowireCandidateResolver().getSuggestedName(descriptor);
- dependencyName = (suggestedName != null && containsBean(suggestedName) ? suggestedName : null);
- }
- if (dependencyName != null &&
- isTypeMatch(dependencyName, type) && isAutowireCandidate(dependencyName, descriptor) &&
- !isFallback(dependencyName) && !hasPrimaryConflict(dependencyName, type) &&
- !isSelfReference(beanName, dependencyName)) {
- if (autowiredBeanNames != null) {
- autowiredBeanNames.add(dependencyName);
+ if (descriptor.usesStandardBeanLookup()) {
+ String dependencyName = descriptor.getDependencyName();
+ if (dependencyName == null || !containsBean(dependencyName)) {
+ String suggestedName = getAutowireCandidateResolver().getSuggestedName(descriptor);
+ dependencyName = (suggestedName != null && containsBean(suggestedName) ? suggestedName : null);
+ }
+ if (dependencyName != null) {
+ dependencyName = canonicalName(dependencyName); // dependency name can be alias of target name
+ if (isTypeMatch(dependencyName, type) && isAutowireCandidate(dependencyName, descriptor) &&
+ !isFallback(dependencyName) && !hasPrimaryConflict(dependencyName, type) &&
+ !isSelfReference(beanName, dependencyName)) {
+ if (autowiredBeanNames != null) {
+ autowiredBeanNames.add(dependencyName);
+ }
+ Object dependencyBean = getBean(dependencyName);
+ return resolveInstance(dependencyBean, descriptor, type, dependencyName);
+ }
}
- Object dependencyBean = getBean(dependencyName);
- return resolveInstance(dependencyBean, descriptor, type, dependencyName);
}
// Step 4a: multiple beans as stream / array / standard collection / plain map
@@ -2020,6 +2024,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, args) :
super.resolveCandidate(beanName, requiredType, beanFactory));
}
+ @Override
+ public boolean usesStandardBeanLookup() {
+ return ObjectUtils.isEmpty(args);
+ }
};
Object result = doResolveDependency(descriptorToUse, beanName, null, null);
return (result instanceof Optional> optional ? optional : Optional.ofNullable(result));
@@ -2101,6 +2109,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
super(original);
increaseNestingLevel();
}
+
+ @Override
+ public boolean usesStandardBeanLookup() {
+ return true;
+ }
}
@@ -2202,6 +2215,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
public boolean isRequired() {
return false;
}
+ @Override
+ public boolean usesStandardBeanLookup() {
+ return true;
+ }
};
return doResolveDependency(descriptorToUse, this.beanName, null, null);
}
@@ -2234,6 +2251,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return false;
}
@Override
+ public boolean usesStandardBeanLookup() {
+ return true;
+ }
+ @Override
@Nullable
public Object resolveNotUnique(ResolvableType type, Map matchingBeans) {
return null;
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
index 6ee8f02e81..fc7a9b04b5 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
@@ -1234,16 +1234,17 @@ class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition tb2 = new RootBeanDefinition(NullFactoryMethods.class);
tb2.setFactoryMethodName("createTestBean");
bf.registerBeanDefinition("testBean2", tb2);
+ bf.registerAlias("testBean2", "testBean");
MapConstructorInjectionBean bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
- assertThat(bean.getTestBeanMap()).hasSize(1);
- assertThat(bean.getTestBeanMap().get("testBean1")).isSameAs(tb1);
- assertThat(bean.getTestBeanMap().get("testBean2")).isNull();
+ assertThat(bean.getTestBean()).hasSize(1);
+ assertThat(bean.getTestBean().get("testBean1")).isSameAs(tb1);
+ assertThat(bean.getTestBean().get("testBean2")).isNull();
bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
- assertThat(bean.getTestBeanMap()).hasSize(1);
- assertThat(bean.getTestBeanMap().get("testBean1")).isSameAs(tb1);
- assertThat(bean.getTestBeanMap().get("testBean2")).isNull();
+ assertThat(bean.getTestBean()).hasSize(1);
+ assertThat(bean.getTestBean().get("testBean1")).isSameAs(tb1);
+ assertThat(bean.getTestBean().get("testBean2")).isNull();
}
@Test
@@ -1255,6 +1256,7 @@ class AutowiredAnnotationBeanPostProcessorTests {
TestBean tb2 = new TestBean("tb2");
bf.registerSingleton("testBean1", tb1);
bf.registerSingleton("testBean2", tb2);
+ bf.registerAlias("testBean1", "testBean");
MapFieldInjectionBean bean = bf.getBean("annotatedBean", MapFieldInjectionBean.class);
assertThat(bean.getTestBeanMap()).hasSize(2);
@@ -1339,9 +1341,9 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("otherMap", new Properties());
MapConstructorInjectionBean bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
- assertThat(bean.getTestBeanMap()).isSameAs(tbm);
+ assertThat(bean.getTestBean()).isSameAs(tbm);
bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
- assertThat(bean.getTestBeanMap()).isSameAs(tbm);
+ assertThat(bean.getTestBean()).isSameAs(tbm);
}
@Test
@@ -1355,9 +1357,9 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.registerSingleton("otherMap", new HashMap<>());
MapConstructorInjectionBean bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
- assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
+ assertThat(bean.getTestBean()).isSameAs(bf.getBean("myTestBeanMap"));
bean = bf.getBean("annotatedBean", MapConstructorInjectionBean.class);
- assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap"));
+ assertThat(bean.getTestBean()).isSameAs(bf.getBean("myTestBeanMap"));
}
@Test
@@ -1578,32 +1580,33 @@ class AutowiredAnnotationBeanPostProcessorTests {
@Test
void objectProviderInjectionWithPrototype() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class));
- RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class);
- tbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
- bf.registerBeanDefinition("testBean", tbd);
+ RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class);
+ tb1.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ bf.registerBeanDefinition("testBean1", tb1);
+ RootBeanDefinition tb2 = new RootBeanDefinition(TestBean.class);
+ tb2.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ tb2.setPrimary(true);
+ bf.registerBeanDefinition("testBean2", tb2);
+ bf.registerAlias("testBean2", "testBean");
ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class);
- assertThat(bean.getTestBean()).isEqualTo(bf.getBean("testBean"));
- assertThat(bean.getTestBean("myName")).isEqualTo(bf.getBean("testBean", "myName"));
- assertThat(bean.getOptionalTestBean()).isEqualTo(bf.getBean("testBean"));
- assertThat(bean.getOptionalTestBeanWithDefault()).isEqualTo(bf.getBean("testBean"));
- assertThat(bean.consumeOptionalTestBean()).isEqualTo(bf.getBean("testBean"));
- assertThat(bean.getUniqueTestBean()).isEqualTo(bf.getBean("testBean"));
- assertThat(bean.getUniqueTestBeanWithDefault()).isEqualTo(bf.getBean("testBean"));
- assertThat(bean.consumeUniqueTestBean()).isEqualTo(bf.getBean("testBean"));
+ assertThat(bean.getTestBean()).isEqualTo(bf.getBean("testBean2"));
+ assertThat(bean.getTestBean("myName")).isEqualTo(bf.getBean("testBean2", "myName"));
+ assertThat(bean.getOptionalTestBean()).isEqualTo(bf.getBean("testBean2"));
+ assertThat(bean.getOptionalTestBeanWithDefault()).isEqualTo(bf.getBean("testBean2"));
+ assertThat(bean.consumeOptionalTestBean()).isEqualTo(bf.getBean("testBean2"));
+ assertThat(bean.getUniqueTestBean()).isEqualTo(bf.getBean("testBean2"));
+ assertThat(bean.getUniqueTestBeanWithDefault()).isEqualTo(bf.getBean("testBean2"));
+ assertThat(bean.consumeUniqueTestBean()).isEqualTo(bf.getBean("testBean2"));
List testBeans = bean.iterateTestBeans();
- assertThat(testBeans).hasSize(1);
- assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
+ assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
testBeans = bean.forEachTestBeans();
- assertThat(testBeans).hasSize(1);
- assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
+ assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
testBeans = bean.streamTestBeans();
- assertThat(testBeans).hasSize(1);
- assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
+ assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
testBeans = bean.sortedTestBeans();
- assertThat(testBeans).hasSize(1);
- assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class));
+ assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class));
}
@Test
@@ -3078,15 +3081,15 @@ class AutowiredAnnotationBeanPostProcessorTests {
public static class MapConstructorInjectionBean {
- private Map testBeanMap;
+ private Map testBean; // matches bean name but should not apply shortcut
@Autowired
- public MapConstructorInjectionBean(Map testBeanMap) {
- this.testBeanMap = testBeanMap;
+ public MapConstructorInjectionBean(Map testBean) {
+ this.testBean = testBean;
}
- public Map getTestBeanMap() {
- return this.testBeanMap;
+ public Map getTestBean() {
+ return this.testBean;
}
}
@@ -3247,47 +3250,47 @@ class AutowiredAnnotationBeanPostProcessorTests {
public static class ObjectProviderInjectionBean {
@Autowired
- private ObjectProvider testBeanProvider;
+ private ObjectProvider testBean; // matches bean name but should not apply shortcut
private TestBean consumedTestBean;
public TestBean getTestBean() {
- return this.testBeanProvider.getObject();
+ return this.testBean.getObject();
}
public TestBean getTestBean(String name) {
- return this.testBeanProvider.getObject(name);
+ return this.testBean.getObject(name);
}
public TestBean getOptionalTestBean() {
- return this.testBeanProvider.getIfAvailable();
+ return this.testBean.getIfAvailable();
}
public TestBean getOptionalTestBeanWithDefault() {
- return this.testBeanProvider.getIfAvailable(() -> new TestBean("default"));
+ return this.testBean.getIfAvailable(() -> new TestBean("default"));
}
public TestBean consumeOptionalTestBean() {
- this.testBeanProvider.ifAvailable(tb -> consumedTestBean = tb);
+ this.testBean.ifAvailable(tb -> consumedTestBean = tb);
return consumedTestBean;
}
public TestBean getUniqueTestBean() {
- return this.testBeanProvider.getIfUnique();
+ return this.testBean.getIfUnique();
}
public TestBean getUniqueTestBeanWithDefault() {
- return this.testBeanProvider.getIfUnique(() -> new TestBean("default"));
+ return this.testBean.getIfUnique(() -> new TestBean("default"));
}
public TestBean consumeUniqueTestBean() {
- this.testBeanProvider.ifUnique(tb -> consumedTestBean = tb);
+ this.testBean.ifUnique(tb -> consumedTestBean = tb);
return consumedTestBean;
}
public List iterateTestBeans() {
List resolved = new ArrayList<>();
- for (TestBean tb : this.testBeanProvider) {
+ for (TestBean tb : this.testBean) {
resolved.add(tb);
}
return resolved;
@@ -3295,16 +3298,16 @@ class AutowiredAnnotationBeanPostProcessorTests {
public List forEachTestBeans() {
List resolved = new ArrayList<>();
- this.testBeanProvider.forEach(resolved::add);
+ this.testBean.forEach(resolved::add);
return resolved;
}
public List streamTestBeans() {
- return this.testBeanProvider.stream().toList();
+ return this.testBean.stream().toList();
}
public List sortedTestBeans() {
- return this.testBeanProvider.orderedStream().toList();
+ return this.testBean.orderedStream().toList();
}
}