diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java index 914b9d9ce79..d147a506561 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java @@ -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); } /** diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index 72592b9cf42..793ba314b4b 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -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); } + } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java index 20729b0c5b3..f0826a62ebe 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java @@ -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. + *

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 nameGenerator() default AnnotationBeanNameGenerator.class; + Class nameGenerator() default BeanNameGenerator.class; /** * The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components. diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java index 9fa82e40c78..a549b8d0af3 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java @@ -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 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) { diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 23dcbe6ef05..02b4125e611 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -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; } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 0825b134dba..1ad4838ed88 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -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); } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 8e724106de8..9736099c6f5 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -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}. + *

Note that this strategy does not apply to {@link Bean} methods. + *

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 + * } 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()); } } } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java index 17b232da595..36a9b1eed8e 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java @@ -40,6 +40,7 @@ public class AsmCircularImportDetectionTests extends AbstractCircularImportDetec new FailFastProblemReporter(), new StandardEnvironment(), new DefaultResourceLoader(), + new AnnotationBeanNameGenerator(), new DefaultListableBeanFactory()); } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java index 799e55a3b68..445397c384d 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationBeanNameTests.java @@ -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 { diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java index 24b35ea7631..02a46b68f83 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java @@ -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); diff --git a/org.springframework.web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java b/org.springframework.web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java index 7f0b27a0d36..d427f7f3811 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java @@ -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() {