Skip to content

Commit fc416bc

Browse files
committed
Apply @configuration BeanNameGenerator consistently
Since the introduction of the AnnotationConfig(Web)ApplicationContext types in Spring 3.0, it has been possible to specify a custom bean name generation strategy via the #setBeanNameGenerator methods available on each of these classes. If specified, that BeanNameGenerator was delegated to the underlying AnnotatedBeanDefinitionReader and ClassPathBeanDefinitionScanner. This meant that any @configuration classes registered or scanned directly from the application context, e.g. via #register or #scan methods would respect the custom name generation strategy as intended. However, for @configuration classes picked up via @import or implicitly registered due to being nested classes would not be aware of this strategy, and would rather fall back to a hard-coded default AnnotationBeanNameGenerator. This change ensures consistent application of custom BeanNameGenerator strategies in the following ways: - Introduction of AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR singleton If a custom BeanNameGenerator is specified via #setBeanNameGenerator the AnnotationConfig* application contexts will, in addition to delegating this object to the underlying reader and scanner, register it as a singleton bean within the enclosing bean factory having the constant name mentioned above. ConfigurationClassPostProcessor now checks for the presence of this singleton, falling back to a default AnnotationBeanNameGenerator if not present. This singleton-based approach is necessary because it is otherwise impossible to parameterize CCPP, given that it is registered as a BeanDefinitionRegistryPostProcessor bean definition in AnnotationConfigUtils#registerAnnotationConfigProcessors - Introduction of ConfigurationClassPostProcessor#setBeanNameGenerator As detailed in the Javadoc for this new method, this allows for customizing the BeanNameGenerator via XML by dropping down to direct registration of CCPP as a <bean> instead of using <context:annotation-config> to enable @configuration class processing. - Smarter defaulting for @componentscan#beanNameGenerator Previously, @componentscan's #beanNameGenerator attribute had a default value of AnnotationBeanNameGenerator. The value is now the BeanNameGenerator interface itself, indicating that the scanner dedicated to processing each @componentscan should fall back to an inherited generator, i.e. the one originally specified against the application context, or the original default provided by ConfigurationClassPostProcessor. This means that name generation strategies will be consistent with a single point of configuration, but that individual @componentscan declarations may still customize the strategy for the beans that are picked up by that particular scanning. Issue: SPR-9124
1 parent e81df2e commit fc416bc

File tree

11 files changed

+140
-26
lines changed

11 files changed

+140
-26
lines changed

org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -109,6 +109,8 @@ public void setEnvironment(ConfigurableEnvironment environment) {
109109
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
110110
this.reader.setBeanNameGenerator(beanNameGenerator);
111111
this.scanner.setBeanNameGenerator(beanNameGenerator);
112+
this.getBeanFactory().registerSingleton(
113+
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
112114
}
113115

114116
/**

org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ public class AnnotationConfigUtils {
5656
public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME =
5757
"org.springframework.context.annotation.internalConfigurationAnnotationProcessor";
5858

59+
/**
60+
* The bean name of the internally managed BeanNameGenerator for use when processing
61+
* {@link Configuration} classes. Set by {@link AnnotationConfigApplicationContext}
62+
* and {@code AnnotationConfigWebApplicationContext} during bootstrap in order to make
63+
* any custom name generation strategy available to the underlying
64+
* {@link ConfigurationClassPostProcessor}.
65+
* @since 3.1.1
66+
*/
67+
public static final String CONFIGURATION_BEAN_NAME_GENERATOR =
68+
"org.springframework.context.annotation.internalConfigurationBeanNameGenerator";
69+
5970
/**
6071
* The bean name of the internally managed Autowired annotation processor.
6172
*/
@@ -249,4 +260,5 @@ static BeanDefinitionHolder applyScopedProxyMode(
249260
return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
250261
}
251262

263+
252264
}

org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -77,8 +77,14 @@
7777
/**
7878
* The {@link BeanNameGenerator} class to be used for naming detected components
7979
* within the Spring container.
80+
* <p>The default value of the {@link BeanNameGenerator} interface itself indicates
81+
* that the scanner used to process this {@code @ComponentScan} annotation should
82+
* use its inherited bean name generator, e.g. the default
83+
* {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
84+
* application context at bootstrap time.
85+
* @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
8086
*/
81-
Class<? extends BeanNameGenerator> nameGenerator() default AnnotationBeanNameGenerator.class;
87+
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
8288

8389
/**
8490
* The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components.

org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,16 @@ class ComponentScanAnnotationParser {
5151

5252
private final BeanDefinitionRegistry registry;
5353

54+
private final BeanNameGenerator beanNameGenerator;
55+
5456

5557
public ComponentScanAnnotationParser(
56-
ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) {
58+
ResourceLoader resourceLoader, Environment environment,
59+
BeanNameGenerator beanNameGenerator, BeanDefinitionRegistry registry) {
60+
5761
this.resourceLoader = resourceLoader;
5862
this.environment = environment;
63+
this.beanNameGenerator = beanNameGenerator;
5964
this.registry = registry;
6065
}
6166

@@ -71,7 +76,10 @@ public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan) {
7176
scanner.setResourceLoader(this.resourceLoader);
7277

7378
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
74-
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
79+
boolean useInheritedGenerator = BeanNameGenerator.class.equals(generatorClass);
80+
scanner.setBeanNameGenerator(useInheritedGenerator
81+
? this.beanNameGenerator
82+
: BeanUtils.instantiateClass(generatorClass));
7583

7684
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
7785
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {

org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.springframework.core.type.AnnotationMetadata;
5050
import org.springframework.core.type.MethodMetadata;
5151
import org.springframework.core.type.classreading.MetadataReaderFactory;
52+
import org.springframework.util.Assert;
5253
import org.springframework.util.StringUtils;
5354

5455
import static org.springframework.context.annotation.MetadataUtils.*;
@@ -82,7 +83,7 @@ class ConfigurationClassBeanDefinitionReader {
8283

8384
private final Environment environment;
8485

85-
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
86+
private final BeanNameGenerator beanNameGenerator;
8687

8788

8889
/**
@@ -91,16 +92,20 @@ class ConfigurationClassBeanDefinitionReader {
9192
* @param problemReporter
9293
* @param metadataReaderFactory
9394
*/
94-
public ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
95+
public ConfigurationClassBeanDefinitionReader(
96+
BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
9597
ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory,
96-
ResourceLoader resourceLoader, Environment environment) {
98+
ResourceLoader resourceLoader, Environment environment,
99+
BeanNameGenerator beanNameGenerator) {
97100

101+
Assert.notNull(beanNameGenerator, "BeanNameGenerator must not be null");
98102
this.registry = registry;
99103
this.sourceExtractor = sourceExtractor;
100104
this.problemReporter = problemReporter;
101105
this.metadataReaderFactory = metadataReaderFactory;
102106
this.resourceLoader = resourceLoader;
103107
this.environment = environment;
108+
this.beanNameGenerator = beanNameGenerator;
104109
}
105110

106111

org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.beans.factory.parsing.ProblemReporter;
3737
import org.springframework.beans.factory.support.BeanDefinitionReader;
3838
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
39+
import org.springframework.beans.factory.support.BeanNameGenerator;
3940
import org.springframework.core.annotation.AnnotationAttributes;
4041
import org.springframework.core.env.Environment;
4142
import org.springframework.core.env.PropertySource;
@@ -100,14 +101,16 @@ class ConfigurationClassParser {
100101
*/
101102
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
102103
ProblemReporter problemReporter, Environment environment,
103-
ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
104+
ResourceLoader resourceLoader, BeanNameGenerator beanNameGenerator,
105+
BeanDefinitionRegistry registry) {
106+
104107
this.metadataReaderFactory = metadataReaderFactory;
105108
this.problemReporter = problemReporter;
106109
this.environment = environment;
107110
this.resourceLoader = resourceLoader;
108111
this.registry = registry;
109-
this.componentScanParser =
110-
new ComponentScanAnnotationParser(this.resourceLoader, this.environment, this.registry);
112+
this.componentScanParser = new ComponentScanAnnotationParser(
113+
resourceLoader, environment, beanNameGenerator, registry);
111114
}
112115

113116

org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
4747
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
4848
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
49+
import org.springframework.beans.factory.support.BeanNameGenerator;
4950
import org.springframework.beans.factory.support.RootBeanDefinition;
5051
import org.springframework.context.EnvironmentAware;
5152
import org.springframework.context.ResourceLoaderAware;
@@ -65,6 +66,8 @@
6566
import org.springframework.util.Assert;
6667
import org.springframework.util.ClassUtils;
6768

69+
import static org.springframework.context.annotation.AnnotationConfigUtils.*;
70+
6871
/**
6972
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
7073
* {@link Configuration @Configuration} classes.
@@ -112,6 +115,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
112115

113116
private ConfigurationClassBeanDefinitionReader reader;
114117

118+
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
119+
115120

116121
/**
117122
* Set the {@link SourceExtractor} to use for generated bean definitions
@@ -142,6 +147,26 @@ public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory
142147
this.setMetadataReaderFactoryCalled = true;
143148
}
144149

150+
/**
151+
* Set the {@link BeanNameGenerator} to be used when registering imported and nested
152+
* {@link Configuration} classes. The default is {@link AnnotationBeanNameGenerator}.
153+
* <p>Note that this strategy does <em>not</em> apply to {@link Bean} methods.
154+
* <p>This setter is typically only appropriate when configuring the post-processor as
155+
* a standalone bean definition in XML, e.g. not using the dedicated
156+
* {@code AnnotationConfig*} application contexts or the {@code
157+
* <context:annotation-config>} element. Any bean name generator specified against
158+
* the application context will take precedence over any value set here.
159+
* @param beanNameGenerator the strategy to use when generating configuration class
160+
* bean names
161+
* @since 3.1.1
162+
* @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
163+
* @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
164+
*/
165+
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
166+
Assert.notNull(beanNameGenerator, "BeanNameGenerator must not be null");
167+
this.beanNameGenerator = beanNameGenerator;
168+
}
169+
145170
public void setEnvironment(Environment environment) {
146171
Assert.notNull(environment, "Environment must not be null");
147172
this.environment = environment;
@@ -197,14 +222,6 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
197222
enhanceConfigurationClasses(beanFactory);
198223
}
199224

200-
private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) {
201-
if (this.reader == null) {
202-
this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor,
203-
this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment);
204-
}
205-
return this.reader;
206-
}
207-
208225
/**
209226
* Build and validate a configuration model based on the registry of
210227
* {@link Configuration} classes.
@@ -223,9 +240,19 @@ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
223240
return;
224241
}
225242

243+
// Detect any custom bean name generation strategy supplied through the enclosing application context
244+
SingletonBeanRegistry singletonRegistry = null;
245+
if (registry instanceof SingletonBeanRegistry) {
246+
singletonRegistry = (SingletonBeanRegistry) registry;
247+
if (singletonRegistry.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
248+
this.beanNameGenerator = (BeanNameGenerator) singletonRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
249+
}
250+
}
251+
226252
// Parse each @Configuration class
227253
ConfigurationClassParser parser = new ConfigurationClassParser(
228-
this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, registry);
254+
this.metadataReaderFactory, this.problemReporter, this.environment,
255+
this.resourceLoader, this.beanNameGenerator, registry);
229256
for (BeanDefinitionHolder holder : configCandidates) {
230257
BeanDefinition bd = holder.getBeanDefinition();
231258
try {
@@ -258,12 +285,18 @@ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
258285
}
259286

260287
// Read the model and create bean definitions based on its content
261-
this.getConfigurationClassBeanDefinitionReader(registry).loadBeanDefinitions(parser.getConfigurationClasses());
288+
if (this.reader == null) {
289+
this.reader = new ConfigurationClassBeanDefinitionReader(
290+
registry, this.sourceExtractor, this.problemReporter,
291+
this.metadataReaderFactory, this.resourceLoader, this.environment,
292+
this.beanNameGenerator);
293+
}
294+
this.reader.loadBeanDefinitions(parser.getConfigurationClasses());
262295

263296
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
264-
if (registry instanceof SingletonBeanRegistry) {
265-
if (!((SingletonBeanRegistry) registry).containsSingleton("importRegistry")) {
266-
((SingletonBeanRegistry) registry).registerSingleton("importRegistry", parser.getImportRegistry());
297+
if (singletonRegistry != null) {
298+
if (!singletonRegistry.containsSingleton("importRegistry")) {
299+
singletonRegistry.registerSingleton("importRegistry", parser.getImportRegistry());
267300
}
268301
}
269302
}

org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ protected ConfigurationClassParser newParser() {
4040
new FailFastProblemReporter(),
4141
new StandardEnvironment(),
4242
new DefaultResourceLoader(),
43+
new AnnotationBeanNameGenerator(),
4344
new DefaultListableBeanFactory());
4445
}
4546

org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131

3232
/**
3333
* Unit tests ensuring that configuration class bean names as expressed via @Configuration
34-
* or @Component 'value' attributes are indeed respected
34+
* or @Component 'value' attributes are indeed respected, and that customization of bean
35+
* naming through a BeanNameGenerator strategy works as expected.
3536
*
3637
* @author Chris Beams
3738
* @since 3.1.1
@@ -60,6 +61,23 @@ public void registerNestedConfig() {
6061
assertThat(ctx.containsBean("nestedBean"), is(true));
6162
}
6263

64+
@Test
65+
public void registerOuterConfig_withBeanNameGenerator() {
66+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
67+
ctx.setBeanNameGenerator(new AnnotationBeanNameGenerator() {
68+
public String generateBeanName(
69+
BeanDefinition definition, BeanDefinitionRegistry registry) {
70+
return "custom-" + super.generateBeanName(definition, registry);
71+
}
72+
});
73+
ctx.register(A.class);
74+
ctx.refresh();
75+
assertThat(ctx.containsBean("custom-outer"), is(true));
76+
assertThat(ctx.containsBean("custom-imported"), is(true));
77+
assertThat(ctx.containsBean("custom-nested"), is(true));
78+
assertThat(ctx.containsBean("nestedBean"), is(true));
79+
}
80+
6381
@Configuration("outer")
6482
@Import(C.class)
6583
static class A {

org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.springframework.beans.factory.support.BeanNameGenerator;
2020
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2121
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
22+
import org.springframework.context.annotation.AnnotationConfigUtils;
2223
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
2324
import org.springframework.context.annotation.ScopeMetadataResolver;
2425
import org.springframework.util.Assert;
@@ -196,6 +197,8 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
196197
if (beanNameGenerator != null) {
197198
reader.setBeanNameGenerator(beanNameGenerator);
198199
scanner.setBeanNameGenerator(beanNameGenerator);
200+
beanFactory.registerSingleton(
201+
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
199202
}
200203
if (scopeMetadataResolver != null) {
201204
reader.setScopeMetadataResolver(scopeMetadataResolver);

0 commit comments

Comments
 (0)