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:
parent
c2b030a50d
commit
d0c31ad84c
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -269,7 +269,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
|
|||
* @return <code>true</code> if the bean can be registered as-is;
|
||||
* <code>false</code> if it should be skipped because there is an
|
||||
* 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
|
||||
*/
|
||||
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
|
||||
|
|
@ -284,7 +284,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
|
|||
if (isCompatible(beanDefinition, existingDef)) {
|
||||
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, " +
|
||||
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,14 +20,15 @@ import java.lang.annotation.Annotation;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
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.BeanNameGenerator;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.core.env.Environment;
|
||||
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.AssignableTypeFilter;
|
||||
import org.springframework.core.type.filter.TypeFilter;
|
||||
|
|
@ -54,15 +55,9 @@ class ComponentScanAnnotationParser {
|
|||
this.registry = registry;
|
||||
}
|
||||
|
||||
public void parse(AnnotationMetadata annotationMetadata) {
|
||||
Map<String, Object> attribs = annotationMetadata.getAnnotationAttributes(ComponentScan.class.getName());
|
||||
if (attribs == null) {
|
||||
// @ComponentScan annotation is not present -> do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
public Set<BeanDefinitionHolder> parse(Map<String, Object> componentScanAttributes) {
|
||||
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");
|
||||
scanner.setEnvironment(this.environment);
|
||||
|
|
@ -71,37 +66,37 @@ class ComponentScanAnnotationParser {
|
|||
scanner.setResourceLoader(this.resourceLoader);
|
||||
|
||||
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) {
|
||||
scanner.setScopedProxyMode(scopedProxyMode);
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
for (Filter filter : (Filter[])attribs.get("excludeFilters")) {
|
||||
for (Filter filter : (Filter[])componentScanAttributes.get("excludeFilters")) {
|
||||
scanner.addExcludeFilter(createTypeFilter(filter));
|
||||
}
|
||||
|
||||
List<String> basePackages = new ArrayList<String>();
|
||||
for (String pkg : (String[])attribs.get("value")) {
|
||||
for (String pkg : (String[])componentScanAttributes.get("value")) {
|
||||
if (StringUtils.hasText(pkg)) {
|
||||
basePackages.add(pkg);
|
||||
}
|
||||
}
|
||||
for (String pkg : (String[])attribs.get("basePackages")) {
|
||||
for (String pkg : (String[])componentScanAttributes.get("basePackages")) {
|
||||
if (StringUtils.hasText(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
|
||||
// weaving may mean we need to revert to stringified class names in
|
||||
// annotation metadata
|
||||
|
|
@ -112,7 +107,7 @@ class ComponentScanAnnotationParser {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import java.util.Set;
|
|||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
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.GenericBeanDefinition;
|
||||
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.ResourceLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
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.MetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -74,15 +66,8 @@ import org.springframework.util.StringUtils;
|
|||
*/
|
||||
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 final BeanDefinitionRegistry registry;
|
||||
|
||||
private final SourceExtractor sourceExtractor;
|
||||
|
|
@ -93,28 +78,21 @@ public class ConfigurationClassBeanDefinitionReader {
|
|||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private final ComponentScanAnnotationParser componentScanParser;
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
|
||||
* to populate the given {@link BeanDefinitionRegistry}.
|
||||
* @param problemReporter
|
||||
* @param metadataReaderFactory
|
||||
*/
|
||||
public ConfigurationClassBeanDefinitionReader(final BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
|
||||
public ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
|
||||
ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory,
|
||||
ResourceLoader resourceLoader, Environment environment) {
|
||||
ResourceLoader resourceLoader) {
|
||||
|
||||
this.registry = registry;
|
||||
this.sourceExtractor = sourceExtractor;
|
||||
this.problemReporter = problemReporter;
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
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
|
||||
*/
|
||||
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
|
||||
AnnotationMetadata metadata = configClass.getMetadata();
|
||||
componentScanParser.parse(metadata);
|
||||
doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass);
|
||||
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
|
||||
loadBeanDefinitionsForBeanMethod(beanMethod);
|
||||
|
|
@ -155,7 +131,7 @@ public class ConfigurationClassBeanDefinitionReader {
|
|||
BeanDefinition configBeanDef = new GenericBeanDefinition();
|
||||
String className = configClass.getMetadata().getClassName();
|
||||
configBeanDef.setBeanClassName(className);
|
||||
if (checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) {
|
||||
if (ConfigurationClassUtils.checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) {
|
||||
String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName((AbstractBeanDefinition)configBeanDef, this.registry);
|
||||
configClass.setBeanName(configBeanName);
|
||||
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
|
||||
* was created from a configuration class as opposed to any other configuration source.
|
||||
|
|
|
|||
|
|
@ -28,11 +28,14 @@ import java.util.Set;
|
|||
import java.util.Stack;
|
||||
|
||||
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.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
|
|
@ -72,16 +75,24 @@ class ConfigurationClassParser {
|
|||
|
||||
private final Environment environment;
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
private final ComponentScanAnnotationParser componentScanParser;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigurationClassParser} instance that will be used
|
||||
* to populate the set of configuration classes.
|
||||
*/
|
||||
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
|
||||
ProblemReporter problemReporter, Environment environment) {
|
||||
ProblemReporter problemReporter, Environment environment,
|
||||
ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
|
||||
this.metadataReaderFactory = metadataReaderFactory;
|
||||
this.problemReporter = problemReporter;
|
||||
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 {
|
||||
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 =
|
||||
AnnotationUtils.findAllAnnotationAttributes(Import.class, metadata.getClassName(), true);
|
||||
for (Map<String, Object> importAttribs : allImportAttribs) {
|
||||
|
|
|
|||
|
|
@ -193,7 +193,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
*/
|
||||
private void processConfigurationClasses(BeanDefinitionRegistry 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);
|
||||
enhanceConfigurationClasses((ConfigurableListableBeanFactory)registry);
|
||||
}
|
||||
|
|
@ -201,7 +202,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) {
|
||||
if (this.reader == null) {
|
||||
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;
|
||||
}
|
||||
|
|
@ -214,7 +215,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
|
||||
for (String beanName : registry.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
|
||||
if (ConfigurationClassBeanDefinitionReader.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
|
||||
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
|
||||
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
|
||||
}
|
||||
}
|
||||
|
|
@ -262,7 +263,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
|
||||
if (ConfigurationClassBeanDefinitionReader.isFullConfigurationClass(beanDef)) {
|
||||
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
|
||||
if (!(beanDef instanceof AbstractBeanDefinition)) {
|
||||
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
|
||||
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,7 +16,9 @@
|
|||
package org.springframework.context.annotation;
|
||||
|
||||
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.core.env.DefaultEnvironment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
|
||||
/**
|
||||
|
|
@ -33,7 +35,12 @@ public class AsmCircularImportDetectionTests extends AbstractCircularImportDetec
|
|||
|
||||
@Override
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue