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 1941d300cca..bc2e9f718ac 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 @@ -1632,7 +1632,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); if (Optional.class == descriptor.getDependencyType()) { - return createOptionalDependency(descriptor, requestingBeanName); + return createOptionalDependency(descriptor, requestingBeanName, autowiredBeanNames, null); } else if (ObjectFactory.class == descriptor.getDependencyType() || ObjectProvider.class == descriptor.getDependencyType()) { @@ -2330,8 +2330,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** * Create an {@link Optional} wrapper for the specified dependency. */ - private Optional createOptionalDependency( - DependencyDescriptor descriptor, @Nullable String beanName, final @Nullable Object... args) { + private Optional createOptionalDependency(DependencyDescriptor descriptor, @Nullable String beanName, + @Nullable Set autowiredBeanNames, @Nullable Object @Nullable [] args) { DependencyDescriptor descriptorToUse = new NestedDependencyDescriptor(descriptor) { @Override @@ -2348,7 +2348,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return ObjectUtils.isEmpty(args); } }; - Object result = doResolveDependency(descriptorToUse, beanName, null, null); + Object result = doResolveDependency(descriptorToUse, beanName, autowiredBeanNames, null); return (result instanceof Optional optional ? optional : Optional.ofNullable(result)); } @@ -2501,12 +2501,18 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto */ private class DependencyObjectProvider implements BeanObjectProvider { + private static final Object NOT_CACHEABLE = new Object(); + + private static final Object NULL_VALUE = new Object(); + private final DependencyDescriptor descriptor; private final boolean optional; private final @Nullable String beanName; + private transient volatile @Nullable Object cachedValue; + public DependencyObjectProvider(DependencyDescriptor descriptor, @Nullable String beanName) { this.descriptor = new NestedDependencyDescriptor(descriptor); this.optional = (this.descriptor.getDependencyType() == Optional.class); @@ -2515,22 +2521,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public Object getObject() throws BeansException { - if (this.optional) { - return createOptionalDependency(this.descriptor, this.beanName); - } - else { - Object result = doResolveDependency(this.descriptor, this.beanName, null, null); - if (result == null) { - throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType()); - } - return result; + Object result = getValue(); + if (result == null) { + throw new NoSuchBeanDefinitionException(this.descriptor.getResolvableType()); } + return result; } @Override public Object getObject(final @Nullable Object... args) throws BeansException { if (this.optional) { - return createOptionalDependency(this.descriptor, this.beanName, args); + return createOptionalDependency(this.descriptor, this.beanName, null, args); } else { DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) { @@ -2551,7 +2552,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto public @Nullable Object getIfAvailable() throws BeansException { try { if (this.optional) { - return createOptionalDependency(this.descriptor, this.beanName); + return createOptionalDependency(this.descriptor, this.beanName, null, null); } else { DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) { @@ -2604,7 +2605,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto }; try { if (this.optional) { - return createOptionalDependency(descriptorToUse, this.beanName); + return createOptionalDependency(descriptorToUse, this.beanName, null, null); } else { return doResolveDependency(descriptorToUse, this.beanName, null, null); @@ -2630,11 +2631,41 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } protected @Nullable Object getValue() throws BeansException { + Object value = this.cachedValue; + if (value == null) { + if (isConfigurationFrozen()) { + Set autowiredBeanNames = new LinkedHashSet<>(2); + value = resolveValue(autowiredBeanNames); + boolean cacheable = false; + if (!autowiredBeanNames.isEmpty()) { + cacheable = true; + for (String autowiredBeanName : autowiredBeanNames) { + if (!containsBean(autowiredBeanName) || !isSingleton(autowiredBeanName)) { + cacheable = false; + } + } + } + this.cachedValue = (cacheable ? (value != null ? value : NULL_VALUE) : NOT_CACHEABLE); + return value; + } + } + else if (value == NULL_VALUE) { + return null; + } + else if (value != NOT_CACHEABLE) { + return value; + } + + // Not cacheable -> fresh resolution. + return resolveValue(null); + } + + private @Nullable Object resolveValue(@Nullable Set autowiredBeanNames) { if (this.optional) { - return createOptionalDependency(this.descriptor, this.beanName); + return createOptionalDependency(this.descriptor, this.beanName, autowiredBeanNames, null); } else { - return doResolveDependency(this.descriptor, this.beanName, null, null); + return doResolveDependency(this.descriptor, this.beanName, autowiredBeanNames, 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 c97e6ce4e09..cf79ab52993 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 @@ -1587,6 +1587,18 @@ class AutowiredAnnotationBeanPostProcessorTests { ObjectFactoryFieldInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryFieldInjectionBean.class); assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + } + + @Test + void objectFactoryFieldInjectionAgainstFrozen() { + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryFieldInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.freezeConfiguration(); + + ObjectFactoryFieldInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryFieldInjectionBean.class); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); } @Test @@ -1596,6 +1608,18 @@ class AutowiredAnnotationBeanPostProcessorTests { ObjectFactoryConstructorInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryConstructorInjectionBean.class); assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + } + + @Test + void objectFactoryConstructorInjectionAgainstFrozen() { + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryConstructorInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.freezeConfiguration(); + + ObjectFactoryConstructorInjectionBean bean = bf.getBean("annotatedBean", ObjectFactoryConstructorInjectionBean.class); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); } @Test @@ -2116,8 +2140,8 @@ class AutowiredAnnotationBeanPostProcessorTests { bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class)); bf.registerSingleton("stringFactoryBean", new StringFactoryBean()); - final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean"); - final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean"); + StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean"); + FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean"); assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull(); assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull(); @@ -2728,9 +2752,11 @@ class AutowiredAnnotationBeanPostProcessorTests { bf.registerSingleton("nonNullBean", "Test"); bf.registerBeanDefinition("mixedNullableInjectionBean", new RootBeanDefinition(MixedNullableInjectionBean.class)); + MixedNullableInjectionBean mixedNullableInjectionBean = bf.getBean(MixedNullableInjectionBean.class); assertThat(mixedNullableInjectionBean.nonNullBean).isNotNull(); assertThat(mixedNullableInjectionBean.nullableBean).isNull(); + assertThat(bf.getDependentBeans("nonNullBean")).contains("mixedNullableInjectionBean"); } @Test @@ -2738,9 +2764,11 @@ class AutowiredAnnotationBeanPostProcessorTests { bf.registerSingleton("nonNullBean", "Test"); bf.registerBeanDefinition("mixedOptionalInjectionBean", new RootBeanDefinition(MixedOptionalInjectionBean.class)); + MixedOptionalInjectionBean mixedOptionalInjectionBean = bf.getBean(MixedOptionalInjectionBean.class); assertThat(mixedOptionalInjectionBean.nonNullBean).isNotNull(); assertThat(mixedOptionalInjectionBean.nullableBean).isNull(); + assertThat(bf.getDependentBeans("nonNullBean")).contains("mixedOptionalInjectionBean"); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java index 901d2729a77..372dba20762 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java @@ -370,8 +370,25 @@ class InjectAnnotationBeanPostProcessorTests { ObjectFactoryFieldInjectionBean bean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + bean = SerializationTestUtils.serializeAndDeserialize(bean); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + } + + @Test + void testObjectFactoryWithBeanFieldAgainstFrozen() throws Exception { + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryFieldInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + bf.freezeConfiguration(); + + ObjectFactoryFieldInjectionBean bean = (ObjectFactoryFieldInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); bean = SerializationTestUtils.serializeAndDeserialize(bean); assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); } @Test @@ -382,8 +399,25 @@ class InjectAnnotationBeanPostProcessorTests { ObjectFactoryMethodInjectionBean bean = (ObjectFactoryMethodInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); bean = SerializationTestUtils.serializeAndDeserialize(bean); assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + } + + @Test + void testObjectFactoryWithBeanMethodAgainstFrozen() throws Exception { + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryMethodInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + bf.freezeConfiguration(); + + ObjectFactoryMethodInjectionBean bean = (ObjectFactoryMethodInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + bean = SerializationTestUtils.serializeAndDeserialize(bean); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); } @Test @@ -444,8 +478,8 @@ class InjectAnnotationBeanPostProcessorTests { bf.registerBeanDefinition("factoryBeanDependentBean", new RootBeanDefinition(FactoryBeanDependentBean.class)); bf.registerSingleton("stringFactoryBean", new StringFactoryBean()); - final StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean"); - final FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean"); + StringFactoryBean factoryBean = (StringFactoryBean) bf.getBean("&stringFactoryBean"); + FactoryBeanDependentBean bean = (FactoryBeanDependentBean) bf.getBean("factoryBeanDependentBean"); assertThat(factoryBean).as("The singleton StringFactoryBean should have been registered.").isNotNull(); assertThat(bean).as("The factoryBeanDependentBean should have been registered.").isNotNull(); @@ -459,6 +493,7 @@ class InjectAnnotationBeanPostProcessorTests { NullableFieldInjectionBean bean = (NullableFieldInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean"); } @Test @@ -476,6 +511,7 @@ class InjectAnnotationBeanPostProcessorTests { NullableMethodInjectionBean bean = (NullableMethodInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean"); } @Test @@ -494,6 +530,7 @@ class InjectAnnotationBeanPostProcessorTests { OptionalFieldInjectionBean bean = (OptionalFieldInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isPresent(); assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean")); + assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean"); } @Test @@ -512,6 +549,7 @@ class InjectAnnotationBeanPostProcessorTests { OptionalMethodInjectionBean bean = (OptionalMethodInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isPresent(); assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean")); + assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean"); } @Test @@ -530,6 +568,7 @@ class InjectAnnotationBeanPostProcessorTests { OptionalListFieldInjectionBean bean = (OptionalListFieldInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).hasValueSatisfying(list -> assertThat(list).containsExactly(bf.getBean("testBean", TestBean.class))); + assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean"); } @Test @@ -548,6 +587,7 @@ class InjectAnnotationBeanPostProcessorTests { OptionalListMethodInjectionBean bean = (OptionalListMethodInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).hasValueSatisfying(list -> assertThat(list).containsExactly(bf.getBean("testBean", TestBean.class))); + assertThat(bf.getDependentBeans("testBean")).contains("annotatedBean"); } @Test @@ -566,6 +606,7 @@ class InjectAnnotationBeanPostProcessorTests { ProviderOfOptionalFieldInjectionBean bean = (ProviderOfOptionalFieldInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isPresent(); assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean")); + assertThat(bf.getDependentBeans("testBean")).doesNotContain("annotatedBean"); } @Test @@ -584,6 +625,7 @@ class InjectAnnotationBeanPostProcessorTests { ProviderOfOptionalMethodInjectionBean bean = (ProviderOfOptionalMethodInjectionBean) bf.getBean("annotatedBean"); assertThat(bean.getTestBean()).isPresent(); assertThat(bean.getTestBean().get()).isSameAs(bf.getBean("testBean")); + assertThat(bf.getDependentBeans("testBean")).doesNotContain("annotatedBean"); } @Test @@ -788,7 +830,6 @@ class InjectAnnotationBeanPostProcessorTests { private ConfigurableListableBeanFactory beanFactory; - public ConstructorResourceInjectionBean() { throw new UnsupportedOperationException(); }