Skip to content

Commit

Permalink
Skip shortcut resolution for non-standard dependency descriptors
Browse files Browse the repository at this point in the history
Closes gh-32326
See gh-28122
  • Loading branch information
jhoeller committed Feb 24, 2024
1 parent 98efa1d commit 567547b
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -332,6 +332,10 @@ public DependencyDescriptor forFallbackMatch() {
public boolean fallbackMatchAllowed() {
return true;
}
@Override
public boolean usesStandardBeanLookup() {
return true;
}
};
}

Expand Down Expand Up @@ -385,6 +389,21 @@ public boolean supportsLazyResolution() {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,11 @@ public Object resolveShortcut(BeanFactory beanFactory) {
String shortcut = this.shortcut;
return (shortcut != null ? beanFactory.getBean(shortcut, getDependencyType()) : null);
}

@Override
public boolean usesStandardBeanLookup() {
return true;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1400,20 +1400,24 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str
}

// 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
Expand Down Expand Up @@ -2020,6 +2024,10 @@ public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFacto
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));
Expand Down Expand Up @@ -2101,6 +2109,11 @@ public NestedDependencyDescriptor(DependencyDescriptor original) {
super(original);
increaseNestingLevel();
}

@Override
public boolean usesStandardBeanLookup() {
return true;
}
}


Expand Down Expand Up @@ -2202,6 +2215,10 @@ public Object getIfAvailable() throws BeansException {
public boolean isRequired() {
return false;
}
@Override
public boolean usesStandardBeanLookup() {
return true;
}
};
return doResolveDependency(descriptorToUse, this.beanName, null, null);
}
Expand Down Expand Up @@ -2234,6 +2251,10 @@ public boolean isRequired() {
return false;
}
@Override
public boolean usesStandardBeanLookup() {
return true;
}
@Override
@Nullable
public Object resolveNotUnique(ResolvableType type, Map<String, Object> matchingBeans) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1234,16 +1234,17 @@ void constructorInjectionWithMap() {
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
Expand All @@ -1255,6 +1256,7 @@ void fieldInjectionWithMap() {
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);
Expand Down Expand Up @@ -1339,9 +1341,9 @@ void constructorInjectionWithTypedMapAsBean() {
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
Expand All @@ -1355,9 +1357,9 @@ void constructorInjectionWithPlainMapAsBean() {
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
Expand Down Expand Up @@ -1578,32 +1580,33 @@ void objectFactorySerialization() throws Exception {
@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
Expand Down Expand Up @@ -3078,15 +3081,15 @@ public static class MyTestBeanSet extends LinkedHashSet<TestBean> {

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;
}
}

Expand Down Expand Up @@ -3247,64 +3250,64 @@ public TestBean getTestBean() {
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;
}

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();
}
}

Expand Down

0 comments on commit 567547b

Please sign in to comment.