From 044258f08554ac9e0b71491e1d3d18f6b1d1e449 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sat, 29 Mar 2025 16:45:39 +0100 Subject: [PATCH] =?UTF-8?q?Support=20abstract=20@=E2=81=A0Configuration=20?= =?UTF-8?q?classes=20without=20@=E2=81=A0Bean=20methods=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Historically, @⁠Configuration classes that did not declare @⁠Bean methods were allowed to be abstract. However, the changes made in 76a6b9ea79 introduced a regression that prevents such classes from being abstract, resulting in a BeanInstantiationException. This change in behavior is caused by the fact that such a @⁠Configuration class is no longer replaced by a concrete subclass created dynamically by CGLIB. This commit restores support for abstract @⁠Configuration classes without @⁠Bean methods by modifying the "no enhancement required" check in ConfigurationClassParser. See gh-34486 Closes gh-34663 --- .../annotation/ConfigurationClassParser.java | 5 ++-- .../ConfigurationClassPostProcessorTests.java | 24 ++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 92d831655c5..525878b3278 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -179,8 +179,9 @@ class ConfigurationClassParser { } // Downgrade to lite (no enhancement) in case of no instance-level @Bean methods. - if (!configClass.hasNonStaticBeanMethods() && ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals( - bd.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE))) { + if (!configClass.getMetadata().isAbstract() && !configClass.hasNonStaticBeanMethods() && + ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals( + bd.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE))) { bd.setAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE, ConfigurationClassUtils.CONFIGURATION_CLASS_LITE); } 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 122111aee98..2add6155bf5 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 @@ -129,6 +129,22 @@ class ConfigurationClassPostProcessorTests { assertThat(beanFactory.getDependentBeans("config")).contains("bar"); } + @Test // gh-34663 + void enhancementIsPresentForAbstractConfigClassWithoutBeanMethods() { + beanFactory.registerBeanDefinition("config", new RootBeanDefinition(AbstractConfigWithoutBeanMethods.class)); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(beanFactory); + RootBeanDefinition beanDefinition = (RootBeanDefinition) beanFactory.getBeanDefinition("config"); + assertThat(beanDefinition.hasBeanClass()).isTrue(); + assertThat(beanDefinition.getBeanClass().getName()).contains(ClassUtils.CGLIB_CLASS_SEPARATOR); + Foo foo = beanFactory.getBean("foo", Foo.class); + Bar bar = beanFactory.getBean("bar", Bar.class); + assertThat(bar.foo).isSameAs(foo); + assertThat(beanFactory.getDependentBeans("foo")).contains("bar"); + String[] dependentsOfSingletonBeanConfig = beanFactory.getDependentBeans(SingletonBeanConfig.class.getName()); + assertThat(dependentsOfSingletonBeanConfig).containsOnly("foo", "bar"); + } + @Test void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalse() { beanFactory.registerBeanDefinition("config", new RootBeanDefinition(NonEnhancedSingletonBeanConfig.class)); @@ -181,7 +197,7 @@ class ConfigurationClassPostProcessorTests { assertThat(bar.foo).isNotSameAs(foo); } - @Test + @Test // gh-34486 void enhancementIsNotPresentWithEmptyConfig() { beanFactory.registerBeanDefinition("config", new RootBeanDefinition(EmptyConfig.class)); ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); @@ -1195,6 +1211,12 @@ class ConfigurationClassPostProcessorTests { } } + @Configuration + @Import(SingletonBeanConfig.class) + abstract static class AbstractConfigWithoutBeanMethods { + // This class intentionally does NOT declare @Bean methods. + } + @Configuration static final class EmptyConfig { }