Skip to content

Commit 5bbeade

Browse files
committed
Detect nested configuration classes even for empty outer classes
Issue: SPR-16839
1 parent 69f14a2 commit 5bbeade

File tree

2 files changed

+118
-3
lines changed

2 files changed

+118
-3
lines changed

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

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ abstract class ConfigurationClassUtils {
7878
* @param metadataReaderFactory the current factory in use by the caller
7979
* @return whether the candidate qualifies as (any kind of) configuration class
8080
*/
81-
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
81+
public static boolean checkConfigurationClassCandidate(
82+
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
83+
8284
String className = beanDef.getBeanClassName();
8385
if (className == null || beanDef.getFactoryMethodName() != null) {
8486
return false;
@@ -103,7 +105,8 @@ else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition)
103105
}
104106
catch (IOException ex) {
105107
if (logger.isDebugEnabled()) {
106-
logger.debug("Could not find class file for introspecting configuration annotations: " + className, ex);
108+
logger.debug("Could not find class file for introspecting configuration annotations: " +
109+
className, ex);
107110
}
108111
return false;
109112
}
@@ -116,7 +119,7 @@ else if (isLiteConfigurationCandidate(metadata)) {
116119
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
117120
}
118121
else {
119-
return false;
122+
return hasNestedConfigurationClass(metadata, metadataReaderFactory);
120123
}
121124

122125
// It's a full or lite configuration candidate... Let's determine the order value, if any.
@@ -128,6 +131,40 @@ else if (isLiteConfigurationCandidate(metadata)) {
128131
return true;
129132
}
130133

134+
/**
135+
* Check whether the specified class declares a nested configuration class.
136+
*/
137+
private static boolean hasNestedConfigurationClass(
138+
AnnotationMetadata metadata, MetadataReaderFactory metadataReaderFactory) {
139+
140+
// Potentially nested configuration classes...
141+
if (metadata instanceof StandardAnnotationMetadata) {
142+
Class<?> beanClass = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
143+
for (Class<?> memberClass : beanClass.getDeclaredClasses()) {
144+
if (isConfigurationCandidate(new StandardAnnotationMetadata(memberClass))) {
145+
return true;
146+
}
147+
}
148+
}
149+
else {
150+
for (String memberName : metadata.getMemberClassNames()) {
151+
try {
152+
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(memberName);
153+
if (isConfigurationCandidate(metadataReader.getAnnotationMetadata())) {
154+
return true;
155+
}
156+
}
157+
catch (IOException ex) {
158+
if (logger.isDebugEnabled()) {
159+
logger.debug("Could not find class file for introspecting configuration annotations: " +
160+
memberName, ex);
161+
}
162+
}
163+
}
164+
}
165+
return false;
166+
}
167+
131168
/**
132169
* Check the given metadata for a configuration class candidate
133170
* (or nested component class declared within a configuration/component class).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.annotation;
18+
19+
import org.junit.Test;
20+
21+
import org.springframework.beans.factory.support.RootBeanDefinition;
22+
23+
/**
24+
* @author Andy Wilkinson
25+
*/
26+
public class ImportVersusDirectRegistrationTests {
27+
28+
@Test
29+
public void thingIsAvailableWhenOuterConfigurationIsRegisteredDirectly() {
30+
try (AnnotationConfigApplicationContext directRegistration = new AnnotationConfigApplicationContext()) {
31+
directRegistration.register(AccidentalLiteConfiguration.class);
32+
directRegistration.refresh();
33+
directRegistration.getBean(Thing.class);
34+
}
35+
}
36+
37+
@Test
38+
public void thingIsAvailableWhenOuterConfigurationIsRegisteredWithClassName() {
39+
try (AnnotationConfigApplicationContext directRegistration = new AnnotationConfigApplicationContext()) {
40+
directRegistration.registerBeanDefinition("config",
41+
new RootBeanDefinition(AccidentalLiteConfiguration.class.getName()));
42+
directRegistration.refresh();
43+
directRegistration.getBean(Thing.class);
44+
}
45+
}
46+
47+
@Test
48+
public void thingIsAvailableWhenOuterConfigurationIsImported() {
49+
try (AnnotationConfigApplicationContext viaImport = new AnnotationConfigApplicationContext()) {
50+
viaImport.register(Importer.class);
51+
viaImport.refresh();
52+
viaImport.getBean(Thing.class);
53+
}
54+
}
55+
56+
}
57+
58+
59+
@Import(AccidentalLiteConfiguration.class)
60+
class Importer {
61+
}
62+
63+
64+
class AccidentalLiteConfiguration {
65+
66+
@Configuration
67+
class InnerConfiguration {
68+
69+
@Bean
70+
public Thing thing() {
71+
return new Thing();
72+
}
73+
}
74+
}
75+
76+
77+
class Thing {
78+
}

0 commit comments

Comments
 (0)