diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index 090846c794..4b7ea48fdd 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -28,11 +28,13 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.stereotype.Component; /** * Utilities for processing @{@link Configuration} classes. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 */ abstract class ConfigurationClassUtils { @@ -48,8 +50,9 @@ abstract class ConfigurationClassUtils { /** - * Check whether the given bean definition is a candidate for a configuration class, - * and mark it accordingly. + * Check whether the given bean definition is a candidate for a configuration class + * (or a nested component class declared within a configuration/component class, + * to be auto-registered as well), and mark it accordingly. * @param beanDef the bean definition to check * @param metadataReaderFactory the current factory in use by the caller * @return whether the candidate qualifies as (any kind of) configuration class @@ -92,22 +95,45 @@ abstract class ConfigurationClassUtils { return false; } + /** + * Check the given metadata for a configuration class candidate + * (or nested component class declared within a configuration/component class). + * @param metadata the metadata of the annotated class + * @return {@code true} if the given class is to be registered as a + * reflection-detected bean definition; {@code false} otherwise + */ public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { return (isFullConfigurationCandidate(metadata) || isLiteConfigurationCandidate(metadata)); } + /** + * Check the given metadata for a full configuration class candidate + * (i.e. a class annotated with {@code @Configuration}). + * @param metadata the metadata of the annotated class + * @return {@code true} if the given class is to be processed as a full + * configuration class, including cross-method call interception + */ public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) { return metadata.isAnnotated(Configuration.class.getName()); } + /** + * Check the given metadata for a lite configuration class candidate + * (i.e. a class annotated with {@code @Component} or just having + * {@code @Import} declarations or {@code @Bean methods}). + * @param metadata the metadata of the annotated class + * @return {@code true} if the given class is to be processed as a lite + * configuration class, just registering it and scanning it for {@code @Bean} methods + */ public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... - return (!metadata.isInterface() && ( + return (!metadata.isInterface() && (metadata.isAnnotated(Component.class.getName()) || metadata.isAnnotated(Import.class.getName()) || metadata.hasAnnotatedMethods(Bean.class.getName()))); } /** - * Determine whether the given bean definition indicates a full @Configuration class. + * Determine whether the given bean definition indicates a full {@code @Configuration} + * class, through checking {@link #checkConfigurationClassCandidate}'s metadata marker. */ public static boolean isFullConfigurationClass(BeanDefinition beanDef) { return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/NestedConfigurationClassTests.java b/spring-context/src/test/java/org/springframework/context/annotation/NestedConfigurationClassTests.java index f4957319d5..bd6d55a1dd 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/NestedConfigurationClassTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/NestedConfigurationClassTests.java @@ -16,19 +16,20 @@ package org.springframework.context.annotation; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; - import org.junit.Test; +import org.springframework.stereotype.Component; import org.springframework.tests.sample.beans.TestBean; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + /** * Tests ensuring that nested static @Configuration classes are automatically detected * and registered without the need for explicit registration or @Import. See SPR-8186. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 */ public class NestedConfigurationClassTests { @@ -89,6 +90,36 @@ public class NestedConfigurationClassTests { assertThat(ctx.getBean("overrideBean", TestBean.class).getName(), is("override-s1")); } + @Test + public void twoLevelsInLiteMode() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(L0ConfigLight.class); + ctx.refresh(); + + ctx.getBean(L0ConfigLight.class); + ctx.getBean("l0Bean"); + + ctx.getBean(L0ConfigLight.L1ConfigLight.class); + ctx.getBean("l1Bean"); + + ctx.getBean(L0ConfigLight.L1ConfigLight.L2ConfigLight.class); + ctx.getBean("l2Bean"); + + // ensure that override order is correct + assertThat(ctx.getBean("overrideBean", TestBean.class).getName(), is("override-l0")); + } + + @Test + public void twoLevelsWithNoBeanMethods() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(L0ConfigEmpty.class); + ctx.refresh(); + + ctx.getBean(L0ConfigEmpty.class); + ctx.getBean(L0ConfigEmpty.L1ConfigEmpty.class); + ctx.getBean(L0ConfigEmpty.L1ConfigEmpty.L2ConfigEmpty.class); + } + @Configuration static class L0Config { @@ -130,6 +161,59 @@ public class NestedConfigurationClassTests { } + @Component + static class L0ConfigLight { + @Bean + public TestBean l0Bean() { + return new TestBean("l0"); + } + + @Bean + public TestBean overrideBean() { + return new TestBean("override-l0"); + } + + @Component + static class L1ConfigLight { + @Bean + public TestBean l1Bean() { + return new TestBean("l1"); + } + + @Bean + public TestBean overrideBean() { + return new TestBean("override-l1"); + } + + @Component + protected static class L2ConfigLight { + @Bean + public TestBean l2Bean() { + return new TestBean("l2"); + } + + @Bean + public TestBean overrideBean() { + return new TestBean("override-l2"); + } + } + } + } + + + @Component + static class L0ConfigEmpty { + + @Component + static class L1ConfigEmpty { + + @Component + protected static class L2ConfigEmpty { + } + } + } + + @Configuration static class S1Config extends L0Config { @Override @@ -139,4 +223,4 @@ public class NestedConfigurationClassTests { } } -} \ No newline at end of file +}