diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/CircularComponentScanException.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/CircularComponentScanException.java new file mode 100644 index 0000000000000000000000000000000000000000..f027dfff30088a4a1627cbda61a53964974caee0 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/CircularComponentScanException.java @@ -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); + } + +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index 9488edf117c87830ed01883f44b30fd1e1994a51..89540c332551add22ef015b35765f6cb8eccd599 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -269,7 +269,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo * @return true if the bean can be registered as-is; * false 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() + "]"); } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java index 3d73bf2d5e950e34c4a3123702b393c208ab0a53..0ec6aa0b4df159a0fe925c188779a4c85ffc80a1 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java @@ -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 attribs = annotationMetadata.getAnnotationAttributes(ComponentScan.class.getName()); - if (attribs == null) { - // @ComponentScan annotation is not present -> do nothing - return; - } - + public Set parse(Map 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 basePackages = new ArrayList(); - 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) { diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 2e4d8dbdc07b5fd79b2889e20313eb916f857071..fd4b13e7d97ba51a03c58eb8afbc906cf9187a31 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -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. diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index e4bc1b4d4f6f7bc37ea62636fe527b666579b42b..b826eb9d88e06e3e59c17fa126912d56ed0fdd00 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -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 componentScanAttributes = metadata.getAnnotationAttributes(ComponentScan.class.getName()); + if (componentScanAttributes != null) { + // the config class is annotated with @ComponentScan -> perform the scan immediately + Set 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> allImportAttribs = AnnotationUtils.findAllAnnotationAttributes(Import.class, metadata.getClassName(), true); for (Map importAttribs : allImportAttribs) { diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index f7a3cede274416574fe021b91c701140f18088ba..23746ef31cd36c8a3cfdf5d63fc4a4d56bb4a544 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -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 configCandidates = new LinkedHashSet(); 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 configBeanDefs = new LinkedHashMap(); 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"); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c3906bb56bc6214f9d59ce351379869b28bd9140 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -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)); + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConflictingBeanDefinitionException.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConflictingBeanDefinitionException.java new file mode 100644 index 0000000000000000000000000000000000000000..03e0c649d62deffdec1e2b131ca36f72d44b4cc7 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConflictingBeanDefinitionException.java @@ -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); + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java index 21fc1e9105c202fac1c71b0289393c45cec7fdcb..8cba62fa6987031dedfa89aca5895c2301e03046 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/AsmCircularImportDetectionTests.java @@ -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 diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationRecursionTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationRecursionTests.java new file mode 100644 index 0000000000000000000000000000000000000000..7493b719057f3b8ee830e82f864b06e39fdb8535 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationRecursionTests.java @@ -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(); + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/cycle/left/LeftConfig.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/cycle/left/LeftConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..edb24f9c556c06b8f0a0ce9e749067c323871241 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/cycle/left/LeftConfig.java @@ -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 { + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/cycle/right/RightConfig.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/cycle/right/RightConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..e602d34c8a5b2835b20e9933b63f16fdab1364ad --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/cycle/right/RightConfig.java @@ -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 { + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/level1/Level1Config.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/level1/Level1Config.java new file mode 100644 index 0000000000000000000000000000000000000000..0ec8a5b757560e3570aa5ee6de7ad4ff4f3c3434 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/level1/Level1Config.java @@ -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"); + } +} \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/level2/Level2Config.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/level2/Level2Config.java new file mode 100644 index 0000000000000000000000000000000000000000..cc8c42f5ed4b7af1f0795b552efda60ed125f788 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/level2/Level2Config.java @@ -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"); + } +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/level3/Level3Component.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/level3/Level3Component.java new file mode 100644 index 0000000000000000000000000000000000000000..4811db8c43ef5389a550828f3f4a9b58fd95ed8c --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/componentscan/level3/Level3Component.java @@ -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 { + +}