From aa56b5001a429ead3ffdeac04cbcbe1d6e6521eb Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 26 Mar 2025 23:47:42 +0100 Subject: [PATCH] Detect late-set primary markers for autowiring shortcut algorithm Closes gh-34658 --- .../factory/support/AbstractBeanFactory.java | 14 +++++++- .../support/DefaultListableBeanFactory.java | 8 +++++ ...wiredAnnotationBeanPostProcessorTests.java | 34 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 6587479b8f5..ad100234639 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -1474,7 +1474,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // Cache the merged bean definition for the time being // (it might still get re-merged later on in order to pick up metadata changes) if (containingBd == null && (isCacheBeanMetadata() || isBeanEligibleForMetadataCaching(beanName))) { - this.mergedBeanDefinitions.put(beanName, mbd); + cacheMergedBeanDefinition(mbd, beanName); } } if (previous != null) { @@ -1503,6 +1503,18 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } } + /** + * Cache the given merged bean definition. + *

Subclasses can override this to derive additional cached state + * from the final post-processed bean definition. + * @param mbd the merged bean definition to cache + * @param beanName the name of the bean + * @since 6.2.6 + */ + protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) { + this.mergedBeanDefinitions.put(beanName, mbd); + } + /** * Check the given merged bean definition, * potentially throwing validation exceptions. 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 0e444f567bc..95b3a058ff0 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 @@ -1019,6 +1019,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return super.obtainInstanceFromSupplier(supplier, beanName, mbd); } + @Override + protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) { + super.cacheMergedBeanDefinition(mbd, beanName); + if (mbd.isPrimary()) { + this.primaryBeanNames.add(beanName); + } + } + @Override protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args) { super.checkMergedBeanDefinition(mbd, beanName, args); 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 82917831f99..fea47ae12c2 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 @@ -1727,6 +1727,40 @@ class AutowiredAnnotationBeanPostProcessorTests { tb2.setFactoryMethodName("newTestBean2"); tb2.setLazyInit(true); bf.registerBeanDefinition("testBean2", tb2); + bf.registerAlias("testBean2", "testBean"); + + ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class); + TestBean testBean1 = bf.getBean("testBean1", TestBean.class); + assertThat(bean.getTestBean()).isSameAs(testBean1); + assertThat(bean.getOptionalTestBean()).isSameAs(testBean1); + assertThat(bean.consumeOptionalTestBean()).isSameAs(testBean1); + assertThat(bean.getUniqueTestBean()).isSameAs(testBean1); + assertThat(bean.consumeUniqueTestBean()).isSameAs(testBean1); + assertThat(bf.containsSingleton("testBean2")).isFalse(); + + TestBean testBean2 = bf.getBean("testBean2", TestBean.class); + assertThat(bean.iterateTestBeans()).containsExactly(testBean1, testBean2); + assertThat(bean.forEachTestBeans()).containsExactly(testBean1, testBean2); + assertThat(bean.streamTestBeans()).containsExactly(testBean1, testBean2); + assertThat(bean.streamTestBeansInOrder()).containsExactly(testBean2, testBean1); + assertThat(bean.allTestBeans()).containsExactly(testBean1, testBean2); + assertThat(bean.allTestBeansInOrder()).containsExactly(testBean2, testBean1); + assertThat(bean.allSingletonBeans()).containsExactly(testBean1, testBean2); + assertThat(bean.allSingletonBeansInOrder()).containsExactly(testBean2, testBean1); + } + + @Test + void objectProviderInjectionWithLateMarkedTargetPrimary() { + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class)); + RootBeanDefinition tb1 = new RootBeanDefinition(TestBeanFactory.class); + tb1.setFactoryMethodName("newTestBean1"); + bf.registerBeanDefinition("testBean1", tb1); + RootBeanDefinition tb2 = new RootBeanDefinition(TestBeanFactory.class); + tb2.setFactoryMethodName("newTestBean2"); + tb2.setLazyInit(true); + bf.registerBeanDefinition("testBean2", tb2); + bf.registerAlias("testBean2", "testBean"); + tb1.setPrimary(true); ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class); TestBean testBean1 = bf.getBean("testBean1", TestBean.class);