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
This commit is contained in:
parent
e81df2ef3e
commit
fc416bcb0b
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -109,6 +109,8 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex
|
|||
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
|
||||
this.reader.setBeanNameGenerator(beanNameGenerator);
|
||||
this.scanner.setBeanNameGenerator(beanNameGenerator);
|
||||
this.getBeanFactory().registerSingleton(
|
||||
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -56,6 +56,17 @@ public class AnnotationConfigUtils {
|
|||
public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME =
|
||||
"org.springframework.context.annotation.internalConfigurationAnnotationProcessor";
|
||||
|
||||
/**
|
||||
* The bean name of the internally managed BeanNameGenerator for use when processing
|
||||
* {@link Configuration} classes. Set by {@link AnnotationConfigApplicationContext}
|
||||
* and {@code AnnotationConfigWebApplicationContext} during bootstrap in order to make
|
||||
* any custom name generation strategy available to the underlying
|
||||
* {@link ConfigurationClassPostProcessor}.
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public static final String CONFIGURATION_BEAN_NAME_GENERATOR =
|
||||
"org.springframework.context.annotation.internalConfigurationBeanNameGenerator";
|
||||
|
||||
/**
|
||||
* The bean name of the internally managed Autowired annotation processor.
|
||||
*/
|
||||
|
@ -249,4 +260,5 @@ public class AnnotationConfigUtils {
|
|||
return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -77,8 +77,14 @@ public @interface ComponentScan {
|
|||
/**
|
||||
* The {@link BeanNameGenerator} class to be used for naming detected components
|
||||
* within the Spring container.
|
||||
* <p>The default value of the {@link BeanNameGenerator} interface itself indicates
|
||||
* that the scanner used to process this {@code @ComponentScan} annotation should
|
||||
* use its inherited bean name generator, e.g. the default
|
||||
* {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
|
||||
* application context at bootstrap time.
|
||||
* @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
|
||||
*/
|
||||
Class<? extends BeanNameGenerator> nameGenerator() default AnnotationBeanNameGenerator.class;
|
||||
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
|
||||
|
||||
/**
|
||||
* The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components.
|
||||
|
|
|
@ -51,11 +51,16 @@ class ComponentScanAnnotationParser {
|
|||
|
||||
private final BeanDefinitionRegistry registry;
|
||||
|
||||
private final BeanNameGenerator beanNameGenerator;
|
||||
|
||||
|
||||
public ComponentScanAnnotationParser(
|
||||
ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) {
|
||||
ResourceLoader resourceLoader, Environment environment,
|
||||
BeanNameGenerator beanNameGenerator, BeanDefinitionRegistry registry) {
|
||||
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.environment = environment;
|
||||
this.beanNameGenerator = beanNameGenerator;
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
|
@ -71,7 +76,10 @@ class ComponentScanAnnotationParser {
|
|||
scanner.setResourceLoader(this.resourceLoader);
|
||||
|
||||
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
|
||||
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
|
||||
boolean useInheritedGenerator = BeanNameGenerator.class.equals(generatorClass);
|
||||
scanner.setBeanNameGenerator(useInheritedGenerator
|
||||
? this.beanNameGenerator
|
||||
: BeanUtils.instantiateClass(generatorClass));
|
||||
|
||||
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
|
||||
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
|
||||
|
|
|
@ -49,6 +49,7 @@ import org.springframework.core.io.ResourceLoader;
|
|||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.context.annotation.MetadataUtils.*;
|
||||
|
@ -82,7 +83,7 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
|
||||
private final Environment environment;
|
||||
|
||||
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
|
||||
private final BeanNameGenerator beanNameGenerator;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -91,16 +92,20 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
* @param problemReporter
|
||||
* @param metadataReaderFactory
|
||||
*/
|
||||
public ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
|
||||
public ConfigurationClassBeanDefinitionReader(
|
||||
BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
|
||||
ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory,
|
||||
ResourceLoader resourceLoader, Environment environment) {
|
||||
ResourceLoader resourceLoader, Environment environment,
|
||||
BeanNameGenerator beanNameGenerator) {
|
||||
|
||||
Assert.notNull(beanNameGenerator, "BeanNameGenerator must not be null");
|
||||
this.registry = registry;
|
||||
this.sourceExtractor = sourceExtractor;
|
||||
this.problemReporter = problemReporter;
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.environment = environment;
|
||||
this.beanNameGenerator = beanNameGenerator;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.springframework.beans.factory.parsing.Problem;
|
|||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReader;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
@ -100,14 +101,16 @@ class ConfigurationClassParser {
|
|||
*/
|
||||
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
|
||||
ProblemReporter problemReporter, Environment environment,
|
||||
ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
|
||||
ResourceLoader resourceLoader, BeanNameGenerator beanNameGenerator,
|
||||
BeanDefinitionRegistry registry) {
|
||||
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.problemReporter = problemReporter;
|
||||
this.environment = environment;
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.registry = registry;
|
||||
this.componentScanParser =
|
||||
new ComponentScanAnnotationParser(this.resourceLoader, this.environment, this.registry);
|
||||
this.componentScanParser = new ComponentScanAnnotationParser(
|
||||
resourceLoader, environment, beanNameGenerator, registry);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
|||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
|
@ -65,6 +66,8 @@ import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.springframework.context.annotation.AnnotationConfigUtils.*;
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
|
||||
* {@link Configuration @Configuration} classes.
|
||||
|
@ -112,6 +115,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
|
||||
private ConfigurationClassBeanDefinitionReader reader;
|
||||
|
||||
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@link SourceExtractor} to use for generated bean definitions
|
||||
|
@ -142,6 +147,26 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
this.setMetadataReaderFactoryCalled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link BeanNameGenerator} to be used when registering imported and nested
|
||||
* {@link Configuration} classes. The default is {@link AnnotationBeanNameGenerator}.
|
||||
* <p>Note that this strategy does <em>not</em> apply to {@link Bean} methods.
|
||||
* <p>This setter is typically only appropriate when configuring the post-processor as
|
||||
* a standalone bean definition in XML, e.g. not using the dedicated
|
||||
* {@code AnnotationConfig*} application contexts or the {@code
|
||||
* <context:annotation-config>} element. Any bean name generator specified against
|
||||
* the application context will take precedence over any value set here.
|
||||
* @param beanNameGenerator the strategy to use when generating configuration class
|
||||
* bean names
|
||||
* @since 3.1.1
|
||||
* @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
|
||||
* @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
|
||||
*/
|
||||
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
|
||||
Assert.notNull(beanNameGenerator, "BeanNameGenerator must not be null");
|
||||
this.beanNameGenerator = beanNameGenerator;
|
||||
}
|
||||
|
||||
public void setEnvironment(Environment environment) {
|
||||
Assert.notNull(environment, "Environment must not be null");
|
||||
this.environment = environment;
|
||||
|
@ -197,14 +222,6 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
enhanceConfigurationClasses(beanFactory);
|
||||
}
|
||||
|
||||
private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) {
|
||||
if (this.reader == null) {
|
||||
this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor,
|
||||
this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment);
|
||||
}
|
||||
return this.reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and validate a configuration model based on the registry of
|
||||
* {@link Configuration} classes.
|
||||
|
@ -223,9 +240,19 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
return;
|
||||
}
|
||||
|
||||
// Detect any custom bean name generation strategy supplied through the enclosing application context
|
||||
SingletonBeanRegistry singletonRegistry = null;
|
||||
if (registry instanceof SingletonBeanRegistry) {
|
||||
singletonRegistry = (SingletonBeanRegistry) registry;
|
||||
if (singletonRegistry.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {
|
||||
this.beanNameGenerator = (BeanNameGenerator) singletonRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse each @Configuration class
|
||||
ConfigurationClassParser parser = new ConfigurationClassParser(
|
||||
this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, registry);
|
||||
this.metadataReaderFactory, this.problemReporter, this.environment,
|
||||
this.resourceLoader, this.beanNameGenerator, registry);
|
||||
for (BeanDefinitionHolder holder : configCandidates) {
|
||||
BeanDefinition bd = holder.getBeanDefinition();
|
||||
try {
|
||||
|
@ -258,12 +285,18 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
}
|
||||
|
||||
// Read the model and create bean definitions based on its content
|
||||
this.getConfigurationClassBeanDefinitionReader(registry).loadBeanDefinitions(parser.getConfigurationClasses());
|
||||
if (this.reader == null) {
|
||||
this.reader = new ConfigurationClassBeanDefinitionReader(
|
||||
registry, this.sourceExtractor, this.problemReporter,
|
||||
this.metadataReaderFactory, this.resourceLoader, this.environment,
|
||||
this.beanNameGenerator);
|
||||
}
|
||||
this.reader.loadBeanDefinitions(parser.getConfigurationClasses());
|
||||
|
||||
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
|
||||
if (registry instanceof SingletonBeanRegistry) {
|
||||
if (!((SingletonBeanRegistry) registry).containsSingleton("importRegistry")) {
|
||||
((SingletonBeanRegistry) registry).registerSingleton("importRegistry", parser.getImportRegistry());
|
||||
if (singletonRegistry != null) {
|
||||
if (!singletonRegistry.containsSingleton("importRegistry")) {
|
||||
singletonRegistry.registerSingleton("importRegistry", parser.getImportRegistry());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ public class AsmCircularImportDetectionTests extends AbstractCircularImportDetec
|
|||
new FailFastProblemReporter(),
|
||||
new StandardEnvironment(),
|
||||
new DefaultResourceLoader(),
|
||||
new AnnotationBeanNameGenerator(),
|
||||
new DefaultListableBeanFactory());
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ import static org.junit.Assert.*;
|
|||
|
||||
/**
|
||||
* Unit tests ensuring that configuration class bean names as expressed via @Configuration
|
||||
* or @Component 'value' attributes are indeed respected
|
||||
* or @Component 'value' attributes are indeed respected, and that customization of bean
|
||||
* naming through a BeanNameGenerator strategy works as expected.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @since 3.1.1
|
||||
|
@ -60,6 +61,23 @@ public class ConfigurationBeanNameTests {
|
|||
assertThat(ctx.containsBean("nestedBean"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerOuterConfig_withBeanNameGenerator() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.setBeanNameGenerator(new AnnotationBeanNameGenerator() {
|
||||
public String generateBeanName(
|
||||
BeanDefinition definition, BeanDefinitionRegistry registry) {
|
||||
return "custom-" + super.generateBeanName(definition, registry);
|
||||
}
|
||||
});
|
||||
ctx.register(A.class);
|
||||
ctx.refresh();
|
||||
assertThat(ctx.containsBean("custom-outer"), is(true));
|
||||
assertThat(ctx.containsBean("custom-imported"), is(true));
|
||||
assertThat(ctx.containsBean("custom-nested"), is(true));
|
||||
assertThat(ctx.containsBean("nestedBean"), is(true));
|
||||
}
|
||||
|
||||
@Configuration("outer")
|
||||
@Import(C.class)
|
||||
static class A {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.web.context.support;
|
|||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
|
||||
import org.springframework.context.annotation.ScopeMetadataResolver;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -196,6 +197,8 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe
|
|||
if (beanNameGenerator != null) {
|
||||
reader.setBeanNameGenerator(beanNameGenerator);
|
||||
scanner.setBeanNameGenerator(beanNameGenerator);
|
||||
beanFactory.registerSingleton(
|
||||
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
|
||||
}
|
||||
if (scopeMetadataResolver != null) {
|
||||
reader.setScopeMetadataResolver(scopeMetadataResolver);
|
||||
|
|
|
@ -19,9 +19,16 @@ package org.springframework.web.context.support;
|
|||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Chris Beams
|
||||
*/
|
||||
|
@ -49,8 +56,24 @@ public class AnnotationConfigWebApplicationContextTests {
|
|||
assertNotNull(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withBeanNameGenerator() {
|
||||
AnnotationConfigWebApplicationContext ctx =
|
||||
new AnnotationConfigWebApplicationContext();
|
||||
ctx.setBeanNameGenerator(new AnnotationBeanNameGenerator() {
|
||||
@Override
|
||||
public String generateBeanName(BeanDefinition definition,
|
||||
BeanDefinitionRegistry registry) {
|
||||
return "custom-" + super.generateBeanName(definition, registry);
|
||||
}
|
||||
});
|
||||
ctx.setConfigLocation(Config.class.getName());
|
||||
ctx.refresh();
|
||||
assertThat(ctx.containsBean("custom-myConfig"), is(true));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
||||
@Configuration("myConfig")
|
||||
static class Config {
|
||||
@Bean
|
||||
public TestBean myTestBean() {
|
||||
|
|
Loading…
Reference in New Issue