From 567547b63c03eb495051b5e68b51182567c6ce61 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 24 Feb 2024 18:05:23 +0100 Subject: [PATCH] Skip shortcut resolution for non-standard dependency descriptors Closes gh-32326 See gh-28122 --- .../factory/config/DependencyDescriptor.java | 21 +++- .../factory/support/ConstructorResolver.java | 5 + .../support/DefaultListableBeanFactory.java | 47 ++++++--- ...wiredAnnotationBeanPostProcessorTests.java | 97 ++++++++++--------- 4 files changed, 109 insertions(+), 61 deletions(-) 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(); } }