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 {}