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 2a1d530382..f1ded578a7 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 @@ -611,13 +611,10 @@ class ConstructorResolver { String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes); throw new BeanCreationException(mbd.getResourceDescription(), beanName, "No matching factory method found on class [" + factoryClass.getName() + "]: " + - (mbd.getFactoryBeanName() != null ? - "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") + + (mbd.getFactoryBeanName() != null ? "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") + "factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " + - "Check that a method with the specified name " + - (minNrOfArgs > 0 ? "and arguments " : "") + - "exists and that it is " + - (isStatic ? "static" : "non-static") + "."); + "Check that a method with the specified name " + (minNrOfArgs > 0 ? "and arguments " : "") + + "exists and that it is " + (isStatic ? "static" : "non-static") + "."); } else if (void.class == factoryMethodToUse.getReturnType()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, @@ -1197,8 +1194,7 @@ class ConstructorResolver { } private Predicate isAssignable(ResolvableType valueType) { - return parameterType -> (valueType == ResolvableType.NONE - || parameterType.isAssignableFrom(valueType)); + return parameterType -> (valueType == ResolvableType.NONE || parameterType.isAssignableFrom(valueType)); } private ResolvableType extractElementType(ResolvableType parameterType) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index a47ac8b1b6..9e7b34dc11 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -613,16 +613,16 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements */ protected void destroyBean(String beanName, @Nullable DisposableBean bean) { // Trigger destruction of dependent beans first... - Set dependencies; + Set dependentBeanNames; synchronized (this.dependentBeanMap) { // Within full synchronization in order to guarantee a disconnected Set - dependencies = this.dependentBeanMap.remove(beanName); + dependentBeanNames = this.dependentBeanMap.remove(beanName); } - if (dependencies != null) { + if (dependentBeanNames != null) { if (logger.isTraceEnabled()) { - logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependencies); + logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependentBeanNames); } - for (String dependentBeanName : dependencies) { + for (String dependentBeanName : dependentBeanNames) { destroySingleton(dependentBeanName); } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java index c3aeec5527..0603f4d619 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.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. @@ -80,15 +80,16 @@ class ComponentScanAnnotationIntegrationTests { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan(example.scannable.PackageMarker.class.getPackage().getName()); ctx.refresh(); + assertContextContainsBean(ctx, "fooServiceImpl"); } @Test void viaContextRegistration() { - AnnotationConfigApplicationContext ctx = - new AnnotationConfigApplicationContext(ComponentScanAnnotatedConfig.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanAnnotatedConfig.class); ctx.getBean(ComponentScanAnnotatedConfig.class); ctx.getBean(TestBean.class); + assertThat(ctx.containsBeanDefinition("componentScanAnnotatedConfig")).as("config class bean not found").isTrue(); assertThat(ctx.containsBean("fooServiceImpl")).as("@ComponentScan annotated @Configuration class registered directly against " + "AnnotationConfigApplicationContext did not trigger component scanning as expected").isTrue(); @@ -96,10 +97,10 @@ class ComponentScanAnnotationIntegrationTests { @Test void viaContextRegistration_WithValueAttribute() { - AnnotationConfigApplicationContext ctx = - new AnnotationConfigApplicationContext(ComponentScanAnnotatedConfig_WithValueAttribute.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanAnnotatedConfig_WithValueAttribute.class); ctx.getBean(ComponentScanAnnotatedConfig_WithValueAttribute.class); ctx.getBean(TestBean.class); + assertThat(ctx.containsBeanDefinition("componentScanAnnotatedConfig_WithValueAttribute")).as("config class bean not found").isTrue(); assertThat(ctx.containsBean("fooServiceImpl")).as("@ComponentScan annotated @Configuration class registered directly against " + "AnnotationConfigApplicationContext did not trigger component scanning as expected").isTrue(); @@ -107,9 +108,9 @@ class ComponentScanAnnotationIntegrationTests { @Test void viaContextRegistration_FromPackageOfConfigClass() { - AnnotationConfigApplicationContext ctx = - new AnnotationConfigApplicationContext(ComponentScanAnnotatedConfigWithImplicitBasePackage.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanAnnotatedConfigWithImplicitBasePackage.class); ctx.getBean(ComponentScanAnnotatedConfigWithImplicitBasePackage.class); + assertThat(ctx.containsBeanDefinition("componentScanAnnotatedConfigWithImplicitBasePackage")).as("config class bean not found").isTrue(); assertThat(ctx.containsBean("scannedComponent")).as("@ComponentScan annotated @Configuration class registered directly against " + "AnnotationConfigApplicationContext did not trigger component scanning as expected").isTrue(); @@ -118,11 +119,12 @@ class ComponentScanAnnotationIntegrationTests { @Test void viaContextRegistration_WithComposedAnnotation() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComposedAnnotationConfig.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(ComposedAnnotationConfig.class); ctx.getBean(ComposedAnnotationConfig.class); ctx.getBean(SimpleComponent.class); ctx.getBean(ClassWithNestedComponents.NestedComponent.class); ctx.getBean(ClassWithNestedComponents.OtherNestedComponent.class); + assertThat(ctx.containsBeanDefinition("componentScanAnnotationIntegrationTests.ComposedAnnotationConfig")).as("config class bean not found").isTrue(); assertThat(ctx.containsBean("simpleComponent")).as("@ComponentScan annotated @Configuration class registered directly against " + "AnnotationConfigApplicationContext did not trigger component scanning as expected").isTrue(); @@ -132,6 +134,7 @@ class ComponentScanAnnotationIntegrationTests { void multipleComposedComponentScanAnnotations() { // gh-30941 ApplicationContext ctx = new AnnotationConfigApplicationContext(MultipleComposedAnnotationsConfig.class); ctx.getBean(MultipleComposedAnnotationsConfig.class); + assertContextContainsBean(ctx, "componentScanAnnotationIntegrationTests.MultipleComposedAnnotationsConfig"); assertContextContainsBean(ctx, "simpleComponent"); assertContextContainsBean(ctx, "barComponent"); @@ -143,7 +146,6 @@ class ComponentScanAnnotationIntegrationTests { assertContextContainsBean(ctx, "componentScanAnnotationIntegrationTests.LocalAnnotationOverridesMultipleMetaAnnotationsConfig"); assertContextContainsBean(ctx, "barComponent"); - assertContextDoesNotContainBean(ctx, "simpleComponent"); assertContextDoesNotContainBean(ctx, "configurableComponent"); } @@ -154,7 +156,6 @@ class ComponentScanAnnotationIntegrationTests { assertContextContainsBean(ctx, "componentScanAnnotationIntegrationTests.LocalAnnotationOverridesMultipleComposedAnnotationsConfig"); assertContextContainsBean(ctx, "barComponent"); - assertContextDoesNotContainBean(ctx, "simpleComponent"); assertContextDoesNotContainBean(ctx, "configurableComponent"); } @@ -166,7 +167,6 @@ class ComponentScanAnnotationIntegrationTests { assertContextContainsBean(ctx, "componentScanAnnotationIntegrationTests.LocalRepeatedAnnotationsOverrideComposedAnnotationsConfig"); assertContextContainsBean(ctx, "barComponent"); assertContextContainsBean(ctx, "configurableComponent"); - assertContextDoesNotContainBean(ctx, "simpleComponent"); } @@ -177,7 +177,6 @@ class ComponentScanAnnotationIntegrationTests { assertContextContainsBean(ctx, "componentScanAnnotationIntegrationTests.LocalRepeatedAnnotationsInContainerOverrideComposedAnnotationsConfig"); assertContextContainsBean(ctx, "barComponent"); assertContextContainsBean(ctx, "configurableComponent"); - assertContextDoesNotContainBean(ctx, "simpleComponent"); } @@ -188,10 +187,12 @@ class ComponentScanAnnotationIntegrationTests { genericBeanDefinition(ComponentScanAnnotatedConfig.class).getBeanDefinition()); bf.registerBeanDefinition("configurationClassPostProcessor", genericBeanDefinition(ConfigurationClassPostProcessor.class).getBeanDefinition()); + GenericApplicationContext ctx = new GenericApplicationContext(bf); ctx.refresh(); ctx.getBean(ComponentScanAnnotatedConfig.class); ctx.getBean(TestBean.class); + assertThat(ctx.containsBeanDefinition("componentScanAnnotatedConfig")).as("config class bean not found").isTrue(); assertThat(ctx.containsBean("fooServiceImpl")).as("@ComponentScan annotated @Configuration class registered as bean " + "definition did not trigger component scanning as expected").isTrue(); @@ -206,7 +207,8 @@ class ComponentScanAnnotationIntegrationTests { @Test void withScopeResolver() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithScopeResolver.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithScopeResolver.class); + // custom scope annotation makes the bean prototype scoped. subsequent calls // to getBean should return distinct instances. assertThat(ctx.getBean(CustomScopeAnnotationBean.class)).isNotSameAs(ctx.getBean(CustomScopeAnnotationBean.class)); @@ -215,7 +217,8 @@ class ComponentScanAnnotationIntegrationTests { @Test void multiComponentScan() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MultiComponentScan.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(MultiComponentScan.class); + assertThat(ctx.getBean(CustomScopeAnnotationBean.class)).isNotSameAs(ctx.getBean(CustomScopeAnnotationBean.class)); assertContextContainsBean(ctx, "scannedComponent"); } @@ -223,14 +226,16 @@ class ComponentScanAnnotationIntegrationTests { @Test void withCustomTypeFilter() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithCustomTypeFilter.class); - assertThat(ctx.getDefaultListableBeanFactory().containsSingleton("componentScanParserTests.KustomAnnotationAutowiredBean")).isFalse(); + + assertThat(ctx.getBeanFactory().containsSingleton("componentScanParserTests.KustomAnnotationAutowiredBean")).isFalse(); KustomAnnotationAutowiredBean testBean = ctx.getBean("componentScanParserTests.KustomAnnotationAutowiredBean", KustomAnnotationAutowiredBean.class); assertThat(testBean.getDependency()).isNotNull(); } @Test void withAwareTypeFilter() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithAwareTypeFilter.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithAwareTypeFilter.class); + assertThat(ctx.getEnvironment().matchesProfiles("the-filter-ran")).isTrue(); } @@ -240,6 +245,7 @@ class ComponentScanAnnotationIntegrationTests { ctx.register(ComponentScanWithScopedProxy.class); ctx.getBeanFactory().registerScope("myScope", new SimpleMapScope()); ctx.refresh(); + // should cast to the interface FooService bean = (FooService) ctx.getBean("scopedProxyTestBean"); // should be dynamic proxy @@ -257,6 +263,7 @@ class ComponentScanAnnotationIntegrationTests { ctx.register(ComponentScanWithScopedProxyThroughRegex.class); ctx.getBeanFactory().registerScope("myScope", new SimpleMapScope()); ctx.refresh(); + // should cast to the interface FooService bean = (FooService) ctx.getBean("scopedProxyTestBean"); // should be dynamic proxy @@ -269,6 +276,7 @@ class ComponentScanAnnotationIntegrationTests { ctx.register(ComponentScanWithScopedProxyThroughAspectJPattern.class); ctx.getBeanFactory().registerScope("myScope", new SimpleMapScope()); ctx.refresh(); + // should cast to the interface FooService bean = (FooService) ctx.getBean("scopedProxyTestBean"); // should be dynamic proxy @@ -277,16 +285,16 @@ class ComponentScanAnnotationIntegrationTests { @Test void withMultipleAnnotationIncludeFilters1() { - AnnotationConfigApplicationContext ctx = - new AnnotationConfigApplicationContext(ComponentScanWithMultipleAnnotationIncludeFilters1.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithMultipleAnnotationIncludeFilters1.class); + ctx.getBean(DefaultNamedComponent.class); // @CustomStereotype-annotated ctx.getBean(MessageBean.class); // @CustomComponent-annotated } @Test void withMultipleAnnotationIncludeFilters2() { - AnnotationConfigApplicationContext ctx = - new AnnotationConfigApplicationContext(ComponentScanWithMultipleAnnotationIncludeFilters2.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithMultipleAnnotationIncludeFilters2.class); + ctx.getBean(DefaultNamedComponent.class); // @CustomStereotype-annotated ctx.getBean(MessageBean.class); // @CustomComponent-annotated } @@ -296,25 +304,28 @@ class ComponentScanAnnotationIntegrationTests { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(ComponentScanWithMultipleAnnotationIncludeFilters3.class); ctx.refresh(); + assertThat(ctx.getBean(DefaultNamedComponent.class).toString()).isEqualTo("overridden"); } @Test void withBeanMethodOverrideAndGeneralOverridingDisabled() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - ctx.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false); + ctx.setAllowBeanDefinitionOverriding(false); ctx.register(ComponentScanWithMultipleAnnotationIncludeFilters3.class); ctx.refresh(); + assertThat(ctx.getBean(DefaultNamedComponent.class).toString()).isEqualTo("overridden"); } @Test void withBasePackagesAndValueAlias() { - AnnotationConfigApplicationContext ctx = - new AnnotationConfigApplicationContext(ComponentScanWithBasePackagesAndValueAlias.class); + ApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithBasePackagesAndValueAlias.class); + assertContextContainsBean(ctx, "fooServiceImpl"); } + private static void assertContextContainsBean(ApplicationContext ctx, String beanName) { assertThat(ctx.containsBean(beanName)).as("context should contain bean " + beanName).isTrue(); } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index 2c3a188ce2..c205ac781a 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -35,6 +35,8 @@ import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.ObjectProvider; @@ -944,8 +946,8 @@ class ConfigurationClassPostProcessorTests { beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(ConcreteConfig.class)); beanFactory.registerBeanDefinition("serviceBeanProvider", new RootBeanDefinition(ServiceBeanProvider.class)); new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); - beanFactory.preInstantiateSingletons(); + beanFactory.preInstantiateSingletons(); beanFactory.getBean(ServiceBean.class); } @@ -958,8 +960,8 @@ class ConfigurationClassPostProcessorTests { beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(ConcreteConfigWithDefaultMethods.class)); beanFactory.registerBeanDefinition("serviceBeanProvider", new RootBeanDefinition(ServiceBeanProvider.class)); new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); - beanFactory.preInstantiateSingletons(); + beanFactory.preInstantiateSingletons(); beanFactory.getBean(ServiceBean.class); } @@ -972,11 +974,25 @@ class ConfigurationClassPostProcessorTests { beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(ConcreteConfigWithDefaultMethods.class.getName())); beanFactory.registerBeanDefinition("serviceBeanProvider", new RootBeanDefinition(ServiceBeanProvider.class.getName())); new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); - beanFactory.preInstantiateSingletons(); + beanFactory.preInstantiateSingletons(); beanFactory.getBean(ServiceBean.class); } + @Test + void testConfigWithFailingInit() { // gh-23343 + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(beanFactory); + beanFactory.addBeanPostProcessor(bpp); + beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()); + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(ConcreteConfigWithFailingInit.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + + assertThatExceptionOfType(BeanCreationException.class).isThrownBy(beanFactory::preInstantiateSingletons); + assertThat(beanFactory.containsSingleton("configClass")).isFalse(); + assertThat(beanFactory.containsSingleton("provider")).isFalse(); + } + @Test void testCircularDependency() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); @@ -985,6 +1001,7 @@ class ConfigurationClassPostProcessorTests { beanFactory.registerBeanDefinition("configClass1", new RootBeanDefinition(A.class)); beanFactory.registerBeanDefinition("configClass2", new RootBeanDefinition(AStrich.class)); new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(beanFactory::preInstantiateSingletons) .withMessageContaining("Circular reference"); @@ -1768,6 +1785,29 @@ class ConfigurationClassPostProcessorTests { } } + @Configuration + public static class ConcreteConfigWithFailingInit implements DefaultMethodsConfig, BeanFactoryAware { + + private BeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Bean + @Override + public ServiceBeanProvider provider() { + return new ServiceBeanProvider(); + } + + @PostConstruct + public void validate() { + beanFactory.getBean("provider"); + throw new IllegalStateException(); + } + } + @Primary public static class ServiceBeanProvider {