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;
|
* @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() + "]");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
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
|
||||||
|
|
|
||||||
|
|
@ -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