Skip shortcut resolution for non-standard dependency descriptors
Closes gh-32326 See gh-28122
This commit is contained in:
@@ -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.
|
||||
* <p>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) {
|
||||
|
||||
@@ -1439,6 +1439,11 @@ class ConstructorResolver {
|
||||
String shortcut = this.shortcut;
|
||||
return (shortcut != null ? beanFactory.getBean(shortcut, getDependencyType()) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesStandardBeanLookup() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<String, Object> matchingBeans) {
|
||||
return null;
|
||||
|
||||
@@ -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<TestBean> 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<String, TestBean> testBeanMap;
|
||||
private Map<String, TestBean> testBean; // matches bean name but should not apply shortcut
|
||||
|
||||
@Autowired
|
||||
public MapConstructorInjectionBean(Map<String, TestBean> testBeanMap) {
|
||||
this.testBeanMap = testBeanMap;
|
||||
public MapConstructorInjectionBean(Map<String, TestBean> testBean) {
|
||||
this.testBean = testBean;
|
||||
}
|
||||
|
||||
public Map<String, TestBean> getTestBeanMap() {
|
||||
return this.testBeanMap;
|
||||
public Map<String, TestBean> getTestBean() {
|
||||
return this.testBean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3247,47 +3250,47 @@ class AutowiredAnnotationBeanPostProcessorTests {
|
||||
public static class ObjectProviderInjectionBean {
|
||||
|
||||
@Autowired
|
||||
private ObjectProvider<TestBean> testBeanProvider;
|
||||
private ObjectProvider<TestBean> 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<TestBean> iterateTestBeans() {
|
||||
List<TestBean> 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<TestBean> forEachTestBeans() {
|
||||
List<TestBean> resolved = new ArrayList<>();
|
||||
this.testBeanProvider.forEach(resolved::add);
|
||||
this.testBean.forEach(resolved::add);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public List<TestBean> streamTestBeans() {
|
||||
return this.testBeanProvider.stream().toList();
|
||||
return this.testBean.stream().toList();
|
||||
}
|
||||
|
||||
public List<TestBean> sortedTestBeans() {
|
||||
return this.testBeanProvider.orderedStream().toList();
|
||||
return this.testBean.orderedStream().toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user