提交 d0c31ad8 编写于 作者: C Chris Beams

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
上级 c2b030a5
/*
* 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);
}
}
\ No newline at end of file
......@@ -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");
......
/*
* 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));
}
}
/*
* 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
......
/*
* 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();
}
}
/*
* 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 {
}
/*
* 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 {
}
/*
* 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");
}
}
\ No newline at end of file
/*
* 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");
}
}
/*
* 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 {
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册