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 7374aa1569b..176fbeb4e60 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 @@ -383,6 +383,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } finally { beanCreation.end(); + if (!isCacheBeanMetadata()) { + clearMergedBeanDefinition(beanName); + } } } @@ -583,7 +586,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp Class[] typesToMatch = (FactoryBean.class == classToMatch ? new Class[] {classToMatch} : new Class[] {FactoryBean.class, classToMatch}); - // Attempt to predict the bean type Class predictedType = null; @@ -1409,7 +1411,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()) { + if (containingBd == null && (isCacheBeanMetadata() || isBeanEligibleForMetadataCaching(beanName))) { this.mergedBeanDefinitions.put(beanName, mbd); } } @@ -1433,6 +1435,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp mbd.factoryMethodReturnType = previous.factoryMethodReturnType; mbd.factoryMethodToIntrospect = previous.factoryMethodToIntrospect; } + if (previous.hasMethodOverrides()) { + mbd.setMethodOverrides(new MethodOverrides(previous.getMethodOverrides())); + } } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java index afa61bd9a60..f482c5cd733 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -53,7 +53,6 @@ public class LookupAnnotationTests { @Test public void testWithoutConstructorArg() { AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); - assertThat(bean).isNotNull(); Object expected = bean.get(); assertThat(expected.getClass()).isEqualTo(TestBean.class); assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean); @@ -62,7 +61,6 @@ public class LookupAnnotationTests { @Test public void testWithOverloadedArg() { AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); - assertThat(bean).isNotNull(); TestBean expected = bean.get("haha"); assertThat(expected.getClass()).isEqualTo(TestBean.class); assertThat(expected.getName()).isEqualTo("haha"); @@ -72,7 +70,6 @@ public class LookupAnnotationTests { @Test public void testWithOneConstructorArg() { AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); - assertThat(bean).isNotNull(); TestBean expected = bean.getOneArgument("haha"); assertThat(expected.getClass()).isEqualTo(TestBean.class); assertThat(expected.getName()).isEqualTo("haha"); @@ -82,7 +79,6 @@ public class LookupAnnotationTests { @Test public void testWithTwoConstructorArg() { AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); - assertThat(bean).isNotNull(); TestBean expected = bean.getTwoArguments("haha", 72); assertThat(expected.getClass()).isEqualTo(TestBean.class); assertThat(expected.getName()).isEqualTo("haha"); @@ -93,7 +89,6 @@ public class LookupAnnotationTests { @Test public void testWithThreeArgsShouldFail() { AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); - assertThat(bean).isNotNull(); assertThatExceptionOfType(AbstractMethodError.class).as("TestBean has no three arg constructor").isThrownBy(() -> bean.getThreeArguments("name", 1, 2)); assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean); @@ -102,7 +97,6 @@ public class LookupAnnotationTests { @Test public void testWithEarlyInjection() { AbstractBean bean = beanFactory.getBean("beanConsumer", BeanConsumer.class).abstractBean; - assertThat(bean).isNotNull(); Object expected = bean.get(); assertThat(expected.getClass()).isEqualTo(TestBean.class); assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean); @@ -115,7 +109,6 @@ public class LookupAnnotationTests { beanFactory.registerBeanDefinition("testBean", tbd); AbstractBean bean = beanFactory.getBean("beanConsumer", BeanConsumer.class).abstractBean; - assertThat(bean).isNotNull(); Object expected = bean.get(); assertThat(expected).isNull(); assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean); @@ -128,7 +121,36 @@ public class LookupAnnotationTests { beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class)); NumberBean bean = (NumberBean) beanFactory.getBean("numberBean"); - assertThat(bean).isNotNull(); + assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore()); + assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore()); + } + + @Test + public void testSingletonWithoutMetadataCaching() { + beanFactory.setCacheBeanMetadata(false); + + beanFactory.registerBeanDefinition("numberBean", new RootBeanDefinition(NumberBean.class)); + beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class)); + beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class)); + + NumberBean bean = (NumberBean) beanFactory.getBean("numberBean"); + assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore()); + assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore()); + } + + @Test + public void testPrototypeWithoutMetadataCaching() { + beanFactory.setCacheBeanMetadata(false); + + beanFactory.registerBeanDefinition("numberBean", new RootBeanDefinition(NumberBean.class, BeanDefinition.SCOPE_PROTOTYPE, null)); + beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class)); + beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class)); + + NumberBean bean = (NumberBean) beanFactory.getBean("numberBean"); + assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore()); + assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore()); + + bean = (NumberBean) beanFactory.getBean("numberBean"); assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore()); assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore()); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/FactoryMethodResolutionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/FactoryMethodResolutionTests.java new file mode 100644 index 00000000000..8c4c67accec --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/FactoryMethodResolutionTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.type.AnnotationMetadata; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Andy Wilkinson + */ +class FactoryMethodResolutionTests { + + @Test + void factoryMethodCanBeResolvedWithBeanMetadataCachingEnabled() { + assertThatFactoryMethodCanBeResolved(true); + } + + @Test + void factoryMethodCanBeResolvedWithBeanMetadataCachingDisabled() { + assertThatFactoryMethodCanBeResolved(false); + } + + private void assertThatFactoryMethodCanBeResolved(boolean cache) { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { + context.getBeanFactory().setCacheBeanMetadata(cache); + context.register(ImportSelectorConfiguration.class); + context.refresh(); + BeanDefinition definition = context.getBeanFactory().getMergedBeanDefinition("exampleBean"); + assertThat(((RootBeanDefinition)definition).getResolvedFactoryMethod()).isNotNull(); + } + } + + + @Configuration + @Import(ExampleImportSelector.class) + static class ImportSelectorConfiguration { + } + + + static class ExampleImportSelector implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] { TestConfiguration.class.getName() }; + } + } + + + @Configuration + static class TestConfiguration { + + @Bean + @ExampleAnnotation + public ExampleBean exampleBean() { + return new ExampleBean(); + } + } + + + static class ExampleBean { + } + + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface ExampleAnnotation { + } + +}