Skip to content

Commit 9bfbe36

Browse files
jhoellerunknown
authored andcommitted
Restored registration of nested component classes (even without factory methods)
Issue: SPR-10865 Issue: SPR-10970
1 parent 190bf24 commit 9bfbe36

File tree

2 files changed

+119
-9
lines changed

2 files changed

+119
-9
lines changed

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
import org.springframework.core.type.StandardAnnotationMetadata;
2929
import org.springframework.core.type.classreading.MetadataReader;
3030
import org.springframework.core.type.classreading.MetadataReaderFactory;
31+
import org.springframework.stereotype.Component;
3132

3233
/**
3334
* Utilities for processing @{@link Configuration} classes.
3435
*
3536
* @author Chris Beams
37+
* @author Juergen Hoeller
3638
* @since 3.1
3739
*/
3840
abstract class ConfigurationClassUtils {
@@ -48,8 +50,9 @@ abstract class ConfigurationClassUtils {
4850

4951

5052
/**
51-
* Check whether the given bean definition is a candidate for a configuration class,
52-
* and mark it accordingly.
53+
* Check whether the given bean definition is a candidate for a configuration class
54+
* (or a nested component class declared within a configuration/component class,
55+
* to be auto-registered as well), and mark it accordingly.
5356
* @param beanDef the bean definition to check
5457
* @param metadataReaderFactory the current factory in use by the caller
5558
* @return whether the candidate qualifies as (any kind of) configuration class
@@ -92,22 +95,45 @@ else if (isLiteConfigurationCandidate(metadata)) {
9295
return false;
9396
}
9497

98+
/**
99+
* Check the given metadata for a configuration class candidate
100+
* (or nested component class declared within a configuration/component class).
101+
* @param metadata the metadata of the annotated class
102+
* @return {@code true} if the given class is to be registered as a
103+
* reflection-detected bean definition; {@code false} otherwise
104+
*/
95105
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
96106
return (isFullConfigurationCandidate(metadata) || isLiteConfigurationCandidate(metadata));
97107
}
98108

109+
/**
110+
* Check the given metadata for a full configuration class candidate
111+
* (i.e. a class annotated with {@code @Configuration}).
112+
* @param metadata the metadata of the annotated class
113+
* @return {@code true} if the given class is to be processed as a full
114+
* configuration class, including cross-method call interception
115+
*/
99116
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
100117
return metadata.isAnnotated(Configuration.class.getName());
101118
}
102119

120+
/**
121+
* Check the given metadata for a lite configuration class candidate
122+
* (i.e. a class annotated with {@code @Component} or just having
123+
* {@code @Import} declarations or {@code @Bean methods}).
124+
* @param metadata the metadata of the annotated class
125+
* @return {@code true} if the given class is to be processed as a lite
126+
* configuration class, just registering it and scanning it for {@code @Bean} methods
127+
*/
103128
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
104129
// Do not consider an interface or an annotation...
105-
return (!metadata.isInterface() && (
130+
return (!metadata.isInterface() && (metadata.isAnnotated(Component.class.getName()) ||
106131
metadata.isAnnotated(Import.class.getName()) || metadata.hasAnnotatedMethods(Bean.class.getName())));
107132
}
108133

109134
/**
110-
* Determine whether the given bean definition indicates a full @Configuration class.
135+
* Determine whether the given bean definition indicates a full {@code @Configuration}
136+
* class, through checking {@link #checkConfigurationClassCandidate}'s metadata marker.
111137
*/
112138
public static boolean isFullConfigurationClass(BeanDefinition beanDef) {
113139
return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));

spring-context/src/test/java/org/springframework/context/annotation/NestedConfigurationClassTests.java

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,20 @@
1616

1717
package org.springframework.context.annotation;
1818

19-
import static org.hamcrest.CoreMatchers.is;
20-
import static org.junit.Assert.assertFalse;
21-
import static org.junit.Assert.assertThat;
22-
2319
import org.junit.Test;
2420

21+
import org.springframework.stereotype.Component;
2522
import org.springframework.tests.sample.beans.TestBean;
2623

24+
import static org.hamcrest.CoreMatchers.*;
25+
import static org.junit.Assert.*;
26+
2727
/**
2828
* Tests ensuring that nested static @Configuration classes are automatically detected
2929
* and registered without the need for explicit registration or @Import. See SPR-8186.
3030
*
3131
* @author Chris Beams
32+
* @author Juergen Hoeller
3233
* @since 3.1
3334
*/
3435
public class NestedConfigurationClassTests {
@@ -89,6 +90,36 @@ public void twoLevelsDeepWithInheritance() {
8990
assertThat(ctx.getBean("overrideBean", TestBean.class).getName(), is("override-s1"));
9091
}
9192

93+
@Test
94+
public void twoLevelsInLiteMode() {
95+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
96+
ctx.register(L0ConfigLight.class);
97+
ctx.refresh();
98+
99+
ctx.getBean(L0ConfigLight.class);
100+
ctx.getBean("l0Bean");
101+
102+
ctx.getBean(L0ConfigLight.L1ConfigLight.class);
103+
ctx.getBean("l1Bean");
104+
105+
ctx.getBean(L0ConfigLight.L1ConfigLight.L2ConfigLight.class);
106+
ctx.getBean("l2Bean");
107+
108+
// ensure that override order is correct
109+
assertThat(ctx.getBean("overrideBean", TestBean.class).getName(), is("override-l0"));
110+
}
111+
112+
@Test
113+
public void twoLevelsWithNoBeanMethods() {
114+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
115+
ctx.register(L0ConfigEmpty.class);
116+
ctx.refresh();
117+
118+
ctx.getBean(L0ConfigEmpty.class);
119+
ctx.getBean(L0ConfigEmpty.L1ConfigEmpty.class);
120+
ctx.getBean(L0ConfigEmpty.L1ConfigEmpty.L2ConfigEmpty.class);
121+
}
122+
92123

93124
@Configuration
94125
static class L0Config {
@@ -130,6 +161,59 @@ public TestBean overrideBean() {
130161
}
131162

132163

164+
@Component
165+
static class L0ConfigLight {
166+
@Bean
167+
public TestBean l0Bean() {
168+
return new TestBean("l0");
169+
}
170+
171+
@Bean
172+
public TestBean overrideBean() {
173+
return new TestBean("override-l0");
174+
}
175+
176+
@Component
177+
static class L1ConfigLight {
178+
@Bean
179+
public TestBean l1Bean() {
180+
return new TestBean("l1");
181+
}
182+
183+
@Bean
184+
public TestBean overrideBean() {
185+
return new TestBean("override-l1");
186+
}
187+
188+
@Component
189+
protected static class L2ConfigLight {
190+
@Bean
191+
public TestBean l2Bean() {
192+
return new TestBean("l2");
193+
}
194+
195+
@Bean
196+
public TestBean overrideBean() {
197+
return new TestBean("override-l2");
198+
}
199+
}
200+
}
201+
}
202+
203+
204+
@Component
205+
static class L0ConfigEmpty {
206+
207+
@Component
208+
static class L1ConfigEmpty {
209+
210+
@Component
211+
protected static class L2ConfigEmpty {
212+
}
213+
}
214+
}
215+
216+
133217
@Configuration
134218
static class S1Config extends L0Config {
135219
@Override
@@ -139,4 +223,4 @@ public TestBean overrideBean() {
139223
}
140224
}
141225

142-
}
226+
}

0 commit comments

Comments
 (0)