diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java index a1052fd6dc6..0c4f43660c4 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java @@ -18,6 +18,7 @@ package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -54,6 +55,7 @@ import org.springframework.core.type.filter.TypeFilter; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented +@Repeatable(ComponentScans.class) public @interface ComponentScan { /** diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java new file mode 100644 index 00000000000..c94985564d6 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScans.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2015 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Container annotation that aggregates several {@link ComponentScan} annotations. + * + *

Can be used natively, declaring several nested {@link ComponentScan} annotations. + * Can also be used in conjunction with Java 8's support for repeatable annotations, + * where {@link ComponentScan} can simply be declared several times on the same method, + * implicitly generating this container annotation. + * + * @author Juergen Hoeller + * @since 4.3 + * @see ComponentScan + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface ComponentScans { + + ComponentScan[] value(); + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index bd96da51e5e..586683a8c09 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -260,15 +260,18 @@ class ConfigurationClassParser { } // Process any @ComponentScan annotations - AnnotationAttributes componentScan = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ComponentScan.class); - if (componentScan != null && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { - // The config class is annotated with @ComponentScan -> perform the scan immediately - Set scannedBeanDefinitions = - this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); - // 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(), this.metadataReaderFactory)) { - parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); + Set componentScans = AnnotationConfigUtils.attributesForRepeatable( + sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); + if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { + for (AnnotationAttributes componentScan : componentScans) { + // The config class is annotated with @ComponentScan -> perform the scan immediately + Set scannedBeanDefinitions = + this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); + // 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(), this.metadataReaderFactory)) { + parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); + } } } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java index b6be49d751c..1a5983b10de 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java @@ -147,7 +147,7 @@ public class ComponentScanAnnotationIntegrationTests { @Test public void withCustomBeanNameGenerator() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - ctx.register(ComponentScanWithBeanNameGenenerator.class); + ctx.register(ComponentScanWithBeanNameGenerator.class); ctx.refresh(); assertThat(ctx.containsBean("custom_fooServiceImpl"), is(true)); assertThat(ctx.containsBean("fooServiceImpl"), is(false)); @@ -159,6 +159,14 @@ public class ComponentScanAnnotationIntegrationTests { // custom scope annotation makes the bean prototype scoped. subsequent calls // to getBean should return distinct instances. assertThat(ctx.getBean(CustomScopeAnnotationBean.class), not(sameInstance(ctx.getBean(CustomScopeAnnotationBean.class)))); + assertThat(ctx.containsBean("scannedComponent"), is(false)); + } + + @Test + public void multiComponentScan() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MultiComponentScan.class); + assertThat(ctx.getBean(CustomScopeAnnotationBean.class), not(sameInstance(ctx.getBean(CustomScopeAnnotationBean.class)))); + assertThat(ctx.containsBean("scannedComponent"), is(true)); } @Test @@ -241,7 +249,8 @@ public class ComponentScanAnnotationIntegrationTests { @ComponentScan @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - public static @interface ComposedConfiguration { + public @interface ComposedConfiguration { + String[] basePackages() default {}; } @@ -253,8 +262,9 @@ public class ComponentScanAnnotationIntegrationTests { @Configuration -@ComponentScan(basePackageClasses=example.scannable._package.class) +@ComponentScan(basePackageClasses = example.scannable._package.class) class ComponentScanAnnotatedConfig { + @Bean public TestBean testBean() { return new TestBean(); @@ -264,6 +274,7 @@ class ComponentScanAnnotatedConfig { @Configuration @ComponentScan("example.scannable") class ComponentScanAnnotatedConfig_WithValueAttribute { + @Bean public TestBean testBean() { return new TestBean(); @@ -272,13 +283,16 @@ class ComponentScanAnnotatedConfig_WithValueAttribute { @Configuration @ComponentScan -class ComponentScanWithNoPackagesConfig {} +class ComponentScanWithNoPackagesConfig { +} @Configuration -@ComponentScan(basePackages="example.scannable", nameGenerator=MyBeanNameGenerator.class) -class ComponentScanWithBeanNameGenenerator {} +@ComponentScan(basePackages = "example.scannable", nameGenerator = MyBeanNameGenerator.class) +class ComponentScanWithBeanNameGenerator { +} class MyBeanNameGenerator extends AnnotationBeanNameGenerator { + @Override public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { return "custom_" + super.generateBeanName(definition, registry); @@ -286,10 +300,18 @@ class MyBeanNameGenerator extends AnnotationBeanNameGenerator { } @Configuration -@ComponentScan(basePackages="example.scannable_scoped", scopeResolver=MyScopeMetadataResolver.class) -class ComponentScanWithScopeResolver {} +@ComponentScan(basePackages = "example.scannable_scoped", scopeResolver = MyScopeMetadataResolver.class) +class ComponentScanWithScopeResolver { +} + +@Configuration +@ComponentScan(basePackages = "example.scannable_scoped", scopeResolver = MyScopeMetadataResolver.class) +@ComponentScan(basePackages = "example.scannable_implicitbasepackage") +class MultiComponentScan { +} class MyScopeMetadataResolver extends AnnotationScopeMetadataResolver { + MyScopeMetadataResolver() { this.scopeAnnotationType = MyScope.class; } @@ -297,13 +319,14 @@ class MyScopeMetadataResolver extends AnnotationScopeMetadataResolver { @Configuration @ComponentScan( - basePackages="org.springframework.context.annotation", - useDefaultFilters=false, + basePackages = "org.springframework.context.annotation", + useDefaultFilters = false, includeFilters = @Filter(type = FilterType.CUSTOM, classes = ComponentScanParserTests.CustomTypeFilter.class), // exclude this class from scanning since it's in the scanned package excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ComponentScanWithCustomTypeFilter.class), lazyInit = true) class ComponentScanWithCustomTypeFilter { + @Bean @SuppressWarnings({ "rawtypes", "serial", "unchecked" }) public static CustomAutowireConfigurer customAutowireConfigurer() { @@ -318,30 +341,30 @@ class ComponentScanWithCustomTypeFilter { } @Configuration -@ComponentScan(basePackages="example.scannable", - scopedProxy=ScopedProxyMode.INTERFACES, - useDefaultFilters=false, +@ComponentScan(basePackages = "example.scannable", + scopedProxy = ScopedProxyMode.INTERFACES, + useDefaultFilters = false, includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ScopedProxyTestBean.class)) class ComponentScanWithScopedProxy {} @Configuration -@ComponentScan(basePackages="example.scannable", - scopedProxy=ScopedProxyMode.INTERFACES, - useDefaultFilters=false, - includeFilters=@Filter(type=FilterType.REGEX, pattern ="((?:[a-z.]+))ScopedProxyTestBean")) +@ComponentScan(basePackages = "example.scannable", + scopedProxy = ScopedProxyMode.INTERFACES, + useDefaultFilters = false, + includeFilters = @Filter(type=FilterType.REGEX, pattern = "((?:[a-z.]+))ScopedProxyTestBean")) class ComponentScanWithScopedProxyThroughRegex {} @Configuration -@ComponentScan(basePackages="example.scannable", - scopedProxy=ScopedProxyMode.INTERFACES, - useDefaultFilters=false, - includeFilters=@Filter(type=FilterType.ASPECTJ, pattern ="*..ScopedProxyTestBean")) +@ComponentScan(basePackages = "example.scannable", + scopedProxy = ScopedProxyMode.INTERFACES, + useDefaultFilters = false, + includeFilters = @Filter(type=FilterType.ASPECTJ, pattern = "*..ScopedProxyTestBean")) class ComponentScanWithScopedProxyThroughAspectJPattern {} @Configuration -@ComponentScan(basePackages="example.scannable", - useDefaultFilters=false, - includeFilters={ +@ComponentScan(basePackages = "example.scannable", + useDefaultFilters = false, + includeFilters = { @Filter(CustomStereotype.class), @Filter(CustomComponent.class) } @@ -349,15 +372,15 @@ class ComponentScanWithScopedProxyThroughAspectJPattern {} class ComponentScanWithMultipleAnnotationIncludeFilters1 {} @Configuration -@ComponentScan(basePackages="example.scannable", - useDefaultFilters=false, - includeFilters=@Filter({CustomStereotype.class, CustomComponent.class}) +@ComponentScan(basePackages = "example.scannable", + useDefaultFilters = false, + includeFilters = @Filter({CustomStereotype.class, CustomComponent.class}) ) class ComponentScanWithMultipleAnnotationIncludeFilters2 {} @Configuration @ComponentScan( - value="example.scannable", - basePackages="example.scannable", - basePackageClasses=example.scannable._package.class) + value = "example.scannable", + basePackages = "example.scannable", + basePackageClasses = example.scannable._package.class) class ComponentScanWithBasePackagesAndValueAlias {}