From 01acb805018da643a3c4320ff0e032aa998beda8 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:19:25 +0200 Subject: [PATCH 1/2] Improve exception handling in ConfigurationClassBeanDefinitionReader Thanks to a proposal from @wilkinsona, this commit introduces a try-catch block in loadBeanDefinitions(...) which throws an IllegalStateException that provides context regarding the configuration class and cause of the failure. Closes gh-35631 Co-authored-by: Andy Wilkinson --- ...onfigurationClassBeanDefinitionReader.java | 8 ++++- .../ConfigurationClassPostProcessorTests.java | 36 +++++++++++++------ .../ConfigurationClassProcessingTests.java | 9 +++-- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 59cbd37af6c..4fef9a3817c 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -117,7 +117,13 @@ class ConfigurationClassBeanDefinitionReader { public void loadBeanDefinitions(Set configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { - loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); + try { + loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); + } + catch (Exception ex) { + throw new IllegalStateException("Failed to load bean definitions for configuration class '" + + configClass.getMetadata().getClassName() + "'", ex); + } } } 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 353c1586957..c497e8960c5 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 @@ -409,23 +409,36 @@ class ConfigurationClassPostProcessorTests { beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class)); beanFactory.setAllowBeanDefinitionOverriding(false); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); - assertThatExceptionOfType(BeanDefinitionStoreException.class) + + assertThatIllegalStateException() .isThrownBy(() -> pp.postProcessBeanFactory(beanFactory)) - .withMessageContaining("bar") - .withMessageContaining("SingletonBeanConfig") - .withMessageContaining(TestBean.class.getName()); + .withMessage("Failed to load bean definitions for configuration class '%s'", SingletonBeanConfig.class.getName()) + .havingCause() + .isInstanceOf(BeanDefinitionStoreException.class) + .withMessageContainingAll( + "bar", + "SingletonBeanConfig", + TestBean.class.getName() + ); } @Test // gh-25430 void detectAliasOverride() { + Class configClass = SecondConfiguration.class; AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory(); beanFactory.setAllowBeanDefinitionOverriding(false); - context.register(FirstConfiguration.class, SecondConfiguration.class); + context.register(FirstConfiguration.class, configClass); + assertThatIllegalStateException().isThrownBy(context::refresh) - .withMessageContaining("alias 'taskExecutor'") - .withMessageContaining("name 'applicationTaskExecutor'") - .withMessageContaining("bean definition 'taskExecutor'"); + .withMessage("Failed to load bean definitions for configuration class '%s'", configClass.getName()) + .havingCause() + .isExactlyInstanceOf(IllegalStateException.class) + .withMessageContainingAll( + "alias 'taskExecutor'", + "name 'applicationTaskExecutor'", + "bean definition 'taskExecutor'" + ); context.close(); } @@ -1158,8 +1171,11 @@ class ConfigurationClassPostProcessorTests { @Test void testNameClashBetweenConfigurationClassAndBean() { - assertThatExceptionOfType(BeanDefinitionStoreException.class) - .isThrownBy(() -> new AnnotationConfigApplicationContext(MyTestBean.class).getBean("myTestBean", TestBean.class)); + assertThatIllegalStateException() + .isThrownBy(() -> new AnnotationConfigApplicationContext(MyTestBean.class)) + .withMessage("Failed to load bean definitions for configuration class '%s'", MyTestBean.class.getName()) + .havingCause() + .isInstanceOf(BeanDefinitionStoreException.class); } @Test diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index 0c50d20f0fd..025f4f5d654 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -60,6 +60,7 @@ import org.springframework.context.support.GenericApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Miscellaneous system tests covering {@link Bean} naming, aliases, scoping and @@ -222,8 +223,12 @@ class ConfigurationClassProcessingTests { @Test // gh-33330 void configurationWithMethodNameMismatch() { - assertThatExceptionOfType(BeanDefinitionOverrideException.class) - .isThrownBy(() -> initBeanFactory(false, ConfigWithMethodNameMismatch.class)); + Class configClass = ConfigWithMethodNameMismatch.class; + assertThatIllegalStateException() + .isThrownBy(() -> initBeanFactory(false, configClass)) + .withMessage("Failed to load bean definitions for configuration class '%s'", configClass.getName()) + .havingCause() + .isInstanceOf(BeanDefinitionOverrideException.class); } @Test // gh-33920 From 1612b7c5dbe53189ed64274571d7c68b61dc4d98 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:28:49 +0200 Subject: [PATCH 2/2] Remove test prefixes --- .../ConfigurationClassPostProcessorTests.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) 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 c497e8960c5..e1153782aea 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 @@ -991,7 +991,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testSelfReferenceExclusionForFactoryMethodOnSameBean() { + void selfReferenceExclusionForFactoryMethodOnSameBean() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(beanFactory); beanFactory.addBeanPostProcessor(bpp); @@ -1005,7 +1005,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testConfigWithDefaultMethods() { + void configWithDefaultMethods() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(beanFactory); beanFactory.addBeanPostProcessor(bpp); @@ -1019,7 +1019,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testConfigWithDefaultMethodsUsingAsm() { + void configWithDefaultMethodsUsingAsm() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(beanFactory); beanFactory.addBeanPostProcessor(bpp); @@ -1033,7 +1033,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testConfigWithFailingInit() { // gh-23343 + void configWithFailingInit() { // gh-23343 AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(beanFactory); beanFactory.addBeanPostProcessor(bpp); @@ -1047,7 +1047,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testCircularDependency() { + void circularDependency() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(beanFactory); beanFactory.addBeanPostProcessor(bpp); @@ -1061,42 +1061,42 @@ class ConfigurationClassPostProcessorTests { } @Test - void testCircularDependencyWithApplicationContext() { + void circularDependencyWithApplicationContext() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> new AnnotationConfigApplicationContext(A.class, AStrich.class)) .withMessageContaining("Circular reference"); } @Test - void testPrototypeArgumentThroughBeanMethodCall() { + void prototypeArgumentThroughBeanMethodCall() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithPrototype.class); ctx.getBean(FooFactory.class).createFoo(new BarArgument()); ctx.close(); } @Test - void testSingletonArgumentThroughBeanMethodCall() { + void singletonArgumentThroughBeanMethodCall() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithSingleton.class); ctx.getBean(FooFactory.class).createFoo(new BarArgument()); ctx.close(); } @Test - void testNullArgumentThroughBeanMethodCall() { + void nullArgumentThroughBeanMethodCall() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanArgumentConfigWithNull.class); ctx.getBean("aFoo"); ctx.close(); } @Test - void testInjectionPointMatchForNarrowTargetReturnType() { + void injectionPointMatchForNarrowTargetReturnType() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(FooBarConfiguration.class); assertThat(ctx.getBean(FooImpl.class).bar).isSameAs(ctx.getBean(BarImpl.class)); ctx.close(); } @Test - void testVarargOnBeanMethod() { + void varargOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class, TestBean.class); VarargConfiguration bean = ctx.getBean(VarargConfiguration.class); assertThat(bean.testBeans).isNotNull(); @@ -1106,7 +1106,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testEmptyVarargOnBeanMethod() { + void emptyVarargOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(VarargConfiguration.class); VarargConfiguration bean = ctx.getBean(VarargConfiguration.class); assertThat(bean.testBeans).isNotNull(); @@ -1115,7 +1115,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testCollectionArgumentOnBeanMethod() { + void collectionArgumentOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class, TestBean.class); CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class); assertThat(bean.testBeans).containsExactly(ctx.getBean(TestBean.class)); @@ -1123,7 +1123,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testEmptyCollectionArgumentOnBeanMethod() { + void emptyCollectionArgumentOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionArgumentConfiguration.class); CollectionArgumentConfiguration bean = ctx.getBean(CollectionArgumentConfiguration.class); assertThat(bean.testBeans).isEmpty(); @@ -1131,7 +1131,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testMapArgumentOnBeanMethod() { + void mapArgumentOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class, DummyRunnable.class); MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class); assertThat(bean.testBeans).hasSize(1).containsValue(ctx.getBean(Runnable.class)); @@ -1139,7 +1139,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testEmptyMapArgumentOnBeanMethod() { + void emptyMapArgumentOnBeanMethod() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapArgumentConfiguration.class); MapArgumentConfiguration bean = ctx.getBean(MapArgumentConfiguration.class); assertThat(bean.testBeans).isEmpty(); @@ -1147,7 +1147,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testCollectionInjectionFromSameConfigurationClass() { + void collectionInjectionFromSameConfigurationClass() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionInjectionConfiguration.class); CollectionInjectionConfiguration bean = ctx.getBean(CollectionInjectionConfiguration.class); assertThat(bean.testBeans).containsExactly(ctx.getBean(TestBean.class)); @@ -1155,7 +1155,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testMapInjectionFromSameConfigurationClass() { + void mapInjectionFromSameConfigurationClass() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(MapInjectionConfiguration.class); MapInjectionConfiguration bean = ctx.getBean(MapInjectionConfiguration.class); assertThat(bean.testBeans).containsOnly(Map.entry("testBean", ctx.getBean(Runnable.class))); @@ -1163,14 +1163,14 @@ class ConfigurationClassPostProcessorTests { } @Test - void testBeanLookupFromSameConfigurationClass() { + void beanLookupFromSameConfigurationClass() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanLookupConfiguration.class); assertThat(ctx.getBean(BeanLookupConfiguration.class).getTestBean()).isSameAs(ctx.getBean(TestBean.class)); ctx.close(); } @Test - void testNameClashBetweenConfigurationClassAndBean() { + void nameClashBetweenConfigurationClassAndBean() { assertThatIllegalStateException() .isThrownBy(() -> new AnnotationConfigApplicationContext(MyTestBean.class)) .withMessage("Failed to load bean definitions for configuration class '%s'", MyTestBean.class.getName()) @@ -1179,7 +1179,7 @@ class ConfigurationClassPostProcessorTests { } @Test - void testBeanDefinitionRegistryPostProcessorConfig() { + void beanDefinitionRegistryPostProcessorConfig() { ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(BeanDefinitionRegistryPostProcessorConfig.class); assertThat(ctx.getBean("myTestBean")).isInstanceOf(TestBean.class); ctx.close();