Allow recursive use of @ComponentScan

Prior to this change, @ComponentScan annotations were only processed at
the first level of depth.  Now, the set of bean definitions resulting
from each declaration of @ComponentScan is checked for configuration
classes that declare @ComponentScan, and recursion is performed as
necessary.

Cycles between @ComponentScan declarations are detected as well. See
CircularComponentScanException.

Issue: SPR-8307
This commit is contained in:
Chris Beams 2011-05-08 13:49:35 +00:00
parent c2b030a50d
commit d0c31ad84c
15 changed files with 429 additions and 108 deletions

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
/**
* Exception thrown upon detection of circular {@link ComponentScan} use.
*
* @author Chris Beams
* @since 3.1
*/
@SuppressWarnings("serial")
class CircularComponentScanException extends IllegalStateException {
public CircularComponentScanException(String message, Exception cause) {
super(message, cause);
}
}

View File

@ -269,7 +269,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
* @return <code>true</code> if the bean can be registered as-is; * @return <code>true</code> if the bean can be registered as-is;
* <code>false</code> if it should be skipped because there is an * <code>false</code> if it should be skipped because there is an
* existing, compatible bean definition for the specified name * existing, compatible bean definition for the specified name
* @throws IllegalStateException if an existing, incompatible * @throws ConflictingBeanDefinitionException if an existing, incompatible
* bean definition has been found for the specified name * bean definition has been found for the specified name
*/ */
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException { protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
@ -284,7 +284,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
if (isCompatible(beanDefinition, existingDef)) { if (isCompatible(beanDefinition, existingDef)) {
return false; return false;
} }
throw new IllegalStateException("Annotation-specified bean name '" + beanName + throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " + "' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]"); "non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
} }

View File

@ -20,14 +20,15 @@ import java.lang.annotation.Annotation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter; import org.springframework.core.type.filter.TypeFilter;
@ -54,15 +55,9 @@ class ComponentScanAnnotationParser {
this.registry = registry; this.registry = registry;
} }
public void parse(AnnotationMetadata annotationMetadata) { public Set<BeanDefinitionHolder> parse(Map<String, Object> componentScanAttributes) {
Map<String, Object> attribs = annotationMetadata.getAnnotationAttributes(ComponentScan.class.getName());
if (attribs == null) {
// @ComponentScan annotation is not present -> do nothing
return;
}
ClassPathBeanDefinitionScanner scanner = ClassPathBeanDefinitionScanner scanner =
new ClassPathBeanDefinitionScanner(registry, (Boolean)attribs.get("useDefaultFilters")); new ClassPathBeanDefinitionScanner(registry, (Boolean)componentScanAttributes.get("useDefaultFilters"));
Assert.notNull(this.environment, "Environment must not be null"); Assert.notNull(this.environment, "Environment must not be null");
scanner.setEnvironment(this.environment); scanner.setEnvironment(this.environment);
@ -71,37 +66,37 @@ class ComponentScanAnnotationParser {
scanner.setResourceLoader(this.resourceLoader); scanner.setResourceLoader(this.resourceLoader);
scanner.setBeanNameGenerator(BeanUtils.instantiateClass( scanner.setBeanNameGenerator(BeanUtils.instantiateClass(
(Class<?>)attribs.get("nameGenerator"), BeanNameGenerator.class)); (Class<?>)componentScanAttributes.get("nameGenerator"), BeanNameGenerator.class));
ScopedProxyMode scopedProxyMode = (ScopedProxyMode) attribs.get("scopedProxy"); ScopedProxyMode scopedProxyMode = (ScopedProxyMode) componentScanAttributes.get("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) { if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode); scanner.setScopedProxyMode(scopedProxyMode);
} else { } else {
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass( scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(
(Class<?>)attribs.get("scopeResolver"), ScopeMetadataResolver.class)); (Class<?>)componentScanAttributes.get("scopeResolver"), ScopeMetadataResolver.class));
} }
scanner.setResourcePattern((String)attribs.get("resourcePattern")); scanner.setResourcePattern((String)componentScanAttributes.get("resourcePattern"));
for (Filter filter : (Filter[])attribs.get("includeFilters")) { for (Filter filter : (Filter[])componentScanAttributes.get("includeFilters")) {
scanner.addIncludeFilter(createTypeFilter(filter)); scanner.addIncludeFilter(createTypeFilter(filter));
} }
for (Filter filter : (Filter[])attribs.get("excludeFilters")) { for (Filter filter : (Filter[])componentScanAttributes.get("excludeFilters")) {
scanner.addExcludeFilter(createTypeFilter(filter)); scanner.addExcludeFilter(createTypeFilter(filter));
} }
List<String> basePackages = new ArrayList<String>(); List<String> basePackages = new ArrayList<String>();
for (String pkg : (String[])attribs.get("value")) { for (String pkg : (String[])componentScanAttributes.get("value")) {
if (StringUtils.hasText(pkg)) { if (StringUtils.hasText(pkg)) {
basePackages.add(pkg); basePackages.add(pkg);
} }
} }
for (String pkg : (String[])attribs.get("basePackages")) { for (String pkg : (String[])componentScanAttributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) { if (StringUtils.hasText(pkg)) {
basePackages.add(pkg); basePackages.add(pkg);
} }
} }
for (Class<?> clazz : (Class<?>[])attribs.get("basePackageClasses")) { for (Class<?> clazz : (Class<?>[])componentScanAttributes.get("basePackageClasses")) {
// TODO: loading user types directly here. implications on load-time // TODO: loading user types directly here. implications on load-time
// weaving may mean we need to revert to stringified class names in // weaving may mean we need to revert to stringified class names in
// annotation metadata // annotation metadata
@ -112,7 +107,7 @@ class ComponentScanAnnotationParser {
throw new IllegalStateException("At least one base package must be specified"); throw new IllegalStateException("At least one base package must be specified");
} }
scanner.scan(basePackages.toArray(new String[]{})); return scanner.doScan(basePackages.toArray(new String[]{}));
} }
private TypeFilter createTypeFilter(Filter filter) { private TypeFilter createTypeFilter(Filter filter) {

View File

@ -27,7 +27,6 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
@ -44,19 +43,12 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Conventions;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -74,13 +66,6 @@ import org.springframework.util.StringUtils;
*/ */
public class ConfigurationClassBeanDefinitionReader { public class ConfigurationClassBeanDefinitionReader {
private static final String CONFIGURATION_CLASS_FULL = "full";
private static final String CONFIGURATION_CLASS_LITE = "lite";
private static final String CONFIGURATION_CLASS_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
private static final Log logger = LogFactory.getLog(ConfigurationClassBeanDefinitionReader.class); private static final Log logger = LogFactory.getLog(ConfigurationClassBeanDefinitionReader.class);
private final BeanDefinitionRegistry registry; private final BeanDefinitionRegistry registry;
@ -93,28 +78,21 @@ public class ConfigurationClassBeanDefinitionReader {
private ResourceLoader resourceLoader; private ResourceLoader resourceLoader;
private Environment environment;
private final ComponentScanAnnotationParser componentScanParser;
/** /**
* Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
* to populate the given {@link BeanDefinitionRegistry}. * to populate the given {@link BeanDefinitionRegistry}.
* @param problemReporter * @param problemReporter
* @param metadataReaderFactory * @param metadataReaderFactory
*/ */
public ConfigurationClassBeanDefinitionReader(final BeanDefinitionRegistry registry, SourceExtractor sourceExtractor, public ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory,
ResourceLoader resourceLoader, Environment environment) { ResourceLoader resourceLoader) {
this.registry = registry; this.registry = registry;
this.sourceExtractor = sourceExtractor; this.sourceExtractor = sourceExtractor;
this.problemReporter = problemReporter; this.problemReporter = problemReporter;
this.metadataReaderFactory = metadataReaderFactory; this.metadataReaderFactory = metadataReaderFactory;
this.resourceLoader = resourceLoader; this.resourceLoader = resourceLoader;
this.environment = environment;
this.componentScanParser = new ComponentScanAnnotationParser(resourceLoader, environment, registry);
} }
@ -133,8 +111,6 @@ public class ConfigurationClassBeanDefinitionReader {
* class itself, all its {@link Bean} methods * class itself, all its {@link Bean} methods
*/ */
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
AnnotationMetadata metadata = configClass.getMetadata();
componentScanParser.parse(metadata);
doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass); doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass);
for (BeanMethod beanMethod : configClass.getBeanMethods()) { for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod); loadBeanDefinitionsForBeanMethod(beanMethod);
@ -155,7 +131,7 @@ public class ConfigurationClassBeanDefinitionReader {
BeanDefinition configBeanDef = new GenericBeanDefinition(); BeanDefinition configBeanDef = new GenericBeanDefinition();
String className = configClass.getMetadata().getClassName(); String className = configClass.getMetadata().getClassName();
configBeanDef.setBeanClassName(className); configBeanDef.setBeanClassName(className);
if (checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) { if (ConfigurationClassUtils.checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) {
String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName((AbstractBeanDefinition)configBeanDef, this.registry); String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName((AbstractBeanDefinition)configBeanDef, this.registry);
configClass.setBeanName(configBeanName); configClass.setBeanName(configBeanName);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -311,59 +287,6 @@ public class ConfigurationClassBeanDefinitionReader {
} }
/**
* Check whether the given bean definition is a candidate for a configuration class,
* and mark it accordingly.
* @param beanDef the bean definition to check
* @param metadataReaderFactory the current factory in use by the caller
* @return whether the candidate qualifies as (any kind of) configuration class
*/
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
AnnotationMetadata metadata = null;
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
metadata = new StandardAnnotationMetadata(((AbstractBeanDefinition) beanDef).getBeanClass());
}
else {
String className = beanDef.getBeanClassName();
if (className != null) {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting factory methods: " + className, ex);
}
return false;
}
}
}
if (metadata != null) {
if (metadata.isAnnotated(Configuration.class.getName())) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
return true;
}
else if (metadata.isAnnotated(Component.class.getName()) ||
metadata.hasAnnotatedMethods(Bean.class.getName())) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
return true;
}
}
return false;
}
/**
* Determine whether the given bean definition indicates a full @Configuration class.
*/
public static boolean isFullConfigurationClass(BeanDefinition beanDef) {
return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
}
/** /**
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition * {@link RootBeanDefinition} marker subclass used to signify that a bean definition
* was created from a configuration class as opposed to any other configuration source. * was created from a configuration class as opposed to any other configuration source.

View File

@ -28,11 +28,14 @@ import java.util.Set;
import java.util.Stack; import java.util.Stack;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata;
@ -72,16 +75,24 @@ class ConfigurationClassParser {
private final Environment environment; private final Environment environment;
private final ResourceLoader resourceLoader;
private final ComponentScanAnnotationParser componentScanParser;
/** /**
* Create a new {@link ConfigurationClassParser} instance that will be used * Create a new {@link ConfigurationClassParser} instance that will be used
* to populate the set of configuration classes. * to populate the set of configuration classes.
*/ */
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
ProblemReporter problemReporter, Environment environment) { ProblemReporter problemReporter, Environment environment,
ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
this.metadataReaderFactory = metadataReaderFactory; this.metadataReaderFactory = metadataReaderFactory;
this.problemReporter = problemReporter; this.problemReporter = problemReporter;
this.environment = environment; this.environment = environment;
this.resourceLoader = resourceLoader;
this.componentScanParser = new ComponentScanAnnotationParser(this.resourceLoader, this.environment, registry);
} }
@ -141,6 +152,25 @@ class ConfigurationClassParser {
} }
protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
Map<String, Object> componentScanAttributes = metadata.getAnnotationAttributes(ComponentScan.class.getName());
if (componentScanAttributes != null) {
// the config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScanAttributes);
// check the set of scanned definitions for any further config classes and parse recursively if necessary
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), metadataReaderFactory)) {
try {
this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
} catch (ConflictingBeanDefinitionException ex) {
throw new CircularComponentScanException(
"A conflicting bean definition was detected while processing @ComponentScan annotations. " +
"This usually indicates a circle between scanned packages.", ex);
}
}
}
}
List<Map<String, Object>> allImportAttribs = List<Map<String, Object>> allImportAttribs =
AnnotationUtils.findAllAnnotationAttributes(Import.class, metadata.getClassName(), true); AnnotationUtils.findAllAnnotationAttributes(Import.class, metadata.getClassName(), true);
for (Map<String, Object> importAttribs : allImportAttribs) { for (Map<String, Object> importAttribs : allImportAttribs) {

View File

@ -193,7 +193,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
*/ */
private void processConfigurationClasses(BeanDefinitionRegistry registry) { private void processConfigurationClasses(BeanDefinitionRegistry registry) {
ConfigurationClassBeanDefinitionReader reader = getConfigurationClassBeanDefinitionReader(registry); ConfigurationClassBeanDefinitionReader reader = getConfigurationClassBeanDefinitionReader(registry);
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment); ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, registry);
processConfigBeanDefinitions(parser, reader, registry); processConfigBeanDefinitions(parser, reader, registry);
enhanceConfigurationClasses((ConfigurableListableBeanFactory)registry); enhanceConfigurationClasses((ConfigurableListableBeanFactory)registry);
} }
@ -201,7 +202,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) { private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) {
if (this.reader == null) { if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader( this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment); registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory, this.resourceLoader);
} }
return this.reader; return this.reader;
} }
@ -214,7 +215,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>(); Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
for (String beanName : registry.getBeanDefinitionNames()) { for (String beanName : registry.getBeanDefinitionNames()) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName); BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassBeanDefinitionReader.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
} }
} }
@ -262,7 +263,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>(); Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
for (String beanName : beanFactory.getBeanDefinitionNames()) { for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (ConfigurationClassBeanDefinitionReader.isFullConfigurationClass(beanDef)) { if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
if (!(beanDef instanceof AbstractBeanDefinition)) { if (!(beanDef instanceof AbstractBeanDefinition)) {
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" + throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
beanName + "' since it is not stored in an AbstractBeanDefinition subclass"); beanName + "' since it is not stored in an AbstractBeanDefinition subclass");

View File

@ -0,0 +1,102 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.core.Conventions;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
/**
* Utilities for processing @{@link Configuration} classes.
*
* @author Chris Beams
* @since 3.1
*/
abstract class ConfigurationClassUtils {
private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class);
private static final String CONFIGURATION_CLASS_FULL = "full";
private static final String CONFIGURATION_CLASS_LITE = "lite";
private static final String CONFIGURATION_CLASS_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
/**
* Check whether the given bean definition is a candidate for a configuration class,
* and mark it accordingly.
* @param beanDef the bean definition to check
* @param metadataReaderFactory the current factory in use by the caller
* @return whether the candidate qualifies as (any kind of) configuration class
*/
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
AnnotationMetadata metadata = null;
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
metadata = new StandardAnnotationMetadata(((AbstractBeanDefinition) beanDef).getBeanClass());
}
else {
String className = beanDef.getBeanClassName();
if (className != null) {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting factory methods: " + className, ex);
}
return false;
}
}
}
if (metadata != null) {
if (metadata.isAnnotated(Configuration.class.getName())) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
return true;
}
else if (metadata.isAnnotated(Component.class.getName()) ||
metadata.hasAnnotatedMethods(Bean.class.getName())) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
return true;
}
}
return false;
}
/**
* Determine whether the given bean definition indicates a full @Configuration class.
*/
public static boolean isFullConfigurationClass(BeanDefinition beanDef) {
return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
/**
* Marker subclass of {@link IllegalStateException}, allowing for explicit
* catch clauses in calling code.
*
* @author Chris Beams
* @since 3.1
*/
@SuppressWarnings("serial")
class ConflictingBeanDefinitionException extends IllegalStateException {
public ConflictingBeanDefinitionException(String message) {
super(message);
}
}

View File

@ -16,7 +16,9 @@
package org.springframework.context.annotation; package org.springframework.context.annotation;
import org.springframework.beans.factory.parsing.FailFastProblemReporter; import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.env.DefaultEnvironment; import org.springframework.core.env.DefaultEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
/** /**
@ -33,7 +35,12 @@ public class AsmCircularImportDetectionTests extends AbstractCircularImportDetec
@Override @Override
protected ConfigurationClassParser newParser() { protected ConfigurationClassParser newParser() {
return new ConfigurationClassParser(new CachingMetadataReaderFactory(), new FailFastProblemReporter(), new DefaultEnvironment()); return new ConfigurationClassParser(
new CachingMetadataReaderFactory(),
new FailFastProblemReporter(),
new DefaultEnvironment(),
new DefaultResourceLoader(),
new DefaultListableBeanFactory());
} }
@Override @Override

View File

@ -0,0 +1,60 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.annotation.componentscan.cycle.left.LeftConfig;
import org.springframework.context.annotation.componentscan.level1.Level1Config;
import org.springframework.context.annotation.componentscan.level2.Level2Config;
import org.springframework.context.annotation.componentscan.level3.Level3Component;
/**
* Tests ensuring that configuration clasess marked with @ComponentScan
* may be processed recursively
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentScanAnnotationRecursionTests {
@Test
public void recursion() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(Level1Config.class);
ctx.refresh();
// assert that all levels have been detected
ctx.getBean(Level1Config.class);
ctx.getBean(Level2Config.class);
ctx.getBean(Level3Component.class);
// assert that enhancement is working
assertThat(ctx.getBean("level1Bean"), sameInstance(ctx.getBean("level1Bean")));
assertThat(ctx.getBean("level2Bean"), sameInstance(ctx.getBean("level2Bean")));
}
@Test(expected=CircularComponentScanException.class)
public void cycleDetection() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(LeftConfig.class);
ctx.refresh();
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation.componentscan.cycle.left;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("org.springframework.context.annotation.componentscan.cycle.right")
public class LeftConfig {
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation.componentscan.cycle.right;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("org.springframework.context.annotation.componentscan.cycle.left")
public class RightConfig {
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation.componentscan.level1;
import org.springframework.beans.TestBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("org.springframework.context.annotation.componentscan.level2")
public class Level1Config {
@Bean
public TestBean level1Bean() {
return new TestBean("level1Bean");
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation.componentscan.level2;
import org.springframework.beans.TestBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("org.springframework.context.annotation.componentscan.level3")
public class Level2Config {
@Bean
public TestBean level2Bean() {
return new TestBean("level2Bean");
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2002-2011 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation.componentscan.level3;
import org.springframework.stereotype.Component;
@Component
public class Level3Component {
}