diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java
index 577ff173514..20729b0c5b3 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java
@@ -144,13 +144,18 @@ public @interface ComponentScan {
FilterType type() default FilterType.ANNOTATION;
/**
- * The class to use as the filter. In the case of {@link FilterType#ANNOTATION},
- * the class will be the annotation itself. In the case of
- * {@link FilterType#ASSIGNABLE_TYPE}, the class will be the type that detected
- * components should be assignable to. And in the case of {@link FilterType#CUSTOM},
- * the class will be an implementation of {@link TypeFilter}.
+ * The class or classes to use as the filter. In the case of
+ * {@link FilterType#ANNOTATION}, the class will be the annotation itself. In the
+ * case of {@link FilterType#ASSIGNABLE_TYPE}, the class will be the type that
+ * detected components should be assignable to. And in the case of
+ * {@link FilterType#CUSTOM}, the class will be an implementation of
+ * {@link TypeFilter}.
+ *
When multiple classes are specified, OR logic is applied, e.g. "include
+ * types annotated with {@code @Foo} OR {@code @Bar}".
+ *
Specifying zero classes is permitted but will have no effect on component
+ * scanning.
*/
- Class> value();
+ Class>[] value(); //doco
}
}
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 0ec6aa0b4df..6042babd37a 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
@@ -78,11 +78,15 @@ class ComponentScanAnnotationParser {
scanner.setResourcePattern((String)componentScanAttributes.get("resourcePattern"));
- for (Filter filter : (Filter[])componentScanAttributes.get("includeFilters")) {
- scanner.addIncludeFilter(createTypeFilter(filter));
+ for (Filter filterAnno : (Filter[])componentScanAttributes.get("includeFilters")) {
+ for (TypeFilter typeFilter : typeFiltersFor(filterAnno)) {
+ scanner.addIncludeFilter(typeFilter);
+ }
}
- for (Filter filter : (Filter[])componentScanAttributes.get("excludeFilters")) {
- scanner.addExcludeFilter(createTypeFilter(filter));
+ for (Filter filterAnno : (Filter[])componentScanAttributes.get("excludeFilters")) {
+ for (TypeFilter typeFilter : typeFiltersFor(filterAnno)) {
+ scanner.addExcludeFilter(typeFilter);
+ }
}
List basePackages = new ArrayList();
@@ -110,18 +114,31 @@ class ComponentScanAnnotationParser {
return scanner.doScan(basePackages.toArray(new String[]{}));
}
- private TypeFilter createTypeFilter(Filter filter) {
- switch (filter.type()) {
- case ANNOTATION:
- @SuppressWarnings("unchecked")
- Class filterClass = (Class)filter.value();
- return new AnnotationTypeFilter(filterClass);
- case ASSIGNABLE_TYPE:
- return new AssignableTypeFilter(filter.value());
- case CUSTOM:
- return BeanUtils.instantiateClass(filter.value(), TypeFilter.class);
- default:
- throw new IllegalArgumentException("unknown filter type " + filter.type());
+ private List typeFiltersFor(Filter filterAnno) {
+ List typeFilters = new ArrayList();
+ for (Class> filterClass : (Class>[])filterAnno.value()) {
+ switch (filterAnno.type()) {
+ case ANNOTATION:
+ Assert.isAssignable(Annotation.class, filterClass,
+ "An error occured when processing a @ComponentScan " +
+ "ANNOTATION type filter: ");
+ @SuppressWarnings("unchecked")
+ Class annoClass = (Class)filterClass;
+ typeFilters.add(new AnnotationTypeFilter(annoClass));
+ break;
+ case ASSIGNABLE_TYPE:
+ typeFilters.add(new AssignableTypeFilter(filterClass));
+ break;
+ case CUSTOM:
+ Assert.isAssignable(TypeFilter.class, filterClass,
+ "An error occured when processing a @ComponentScan " +
+ "CUSTOM type filter: ");
+ typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
+ break;
+ default:
+ throw new IllegalArgumentException("unknown filter type " + filterAnno.type());
+ }
}
+ return typeFilters;
}
}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
index ab425604c3a..9ed43a3bbf4 100644
--- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
@@ -41,7 +41,11 @@ import org.springframework.context.annotation.ComponentScanParserTests.CustomAnn
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.util.SerializationTestUtils;
+import example.scannable.CustomComponent;
+import example.scannable.CustomStereotype;
+import example.scannable.DefaultNamedComponent;
import example.scannable.FooService;
+import example.scannable.MessageBean;
import example.scannable.ScopedProxyTestBean;
import example.scannable_scoped.CustomScopeAnnotationBean;
import example.scannable_scoped.MyScope;
@@ -159,6 +163,24 @@ public class ComponentScanAnnotationIntegrationTests {
assertThat(deserialized.foo(1), equalTo("bar"));
}
+ @Test
+ public void withMultipleAnnotationIncludeFilters1() throws IOException, ClassNotFoundException {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(ComponentScanWithMultipleAnnotationIncludeFilters1.class);
+ ctx.refresh();
+ ctx.getBean(DefaultNamedComponent.class); // @CustomStereotype-annotated
+ ctx.getBean(MessageBean.class); // @CustomComponent-annotated
+ }
+
+ @Test
+ public void withMultipleAnnotationIncludeFilters2() throws IOException, ClassNotFoundException {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(ComponentScanWithMultipleAnnotationIncludeFilters2.class);
+ ctx.refresh();
+ ctx.getBean(DefaultNamedComponent.class); // @CustomStereotype-annotated
+ ctx.getBean(MessageBean.class); // @CustomComponent-annotated
+ }
+
@Test
public void withBasePackagesAndValueAlias() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
@@ -222,7 +244,7 @@ class MyScopeMetadataResolver extends AnnotationScopeMetadataResolver {
class ComponentScanWithCustomTypeFilter {
@Bean
@SuppressWarnings({ "rawtypes", "serial", "unchecked" })
- public CustomAutowireConfigurer customAutowireConfigurer() {
+ public static CustomAutowireConfigurer customAutowireConfigurer() {
CustomAutowireConfigurer cac = new CustomAutowireConfigurer();
cac.setCustomQualifierTypes(new HashSet() {{ add(ComponentScanParserTests.CustomAnnotation.class); }});
return cac;
@@ -240,6 +262,23 @@ class ComponentScanWithCustomTypeFilter {
includeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=ScopedProxyTestBean.class))
class ComponentScanWithScopedProxy { }
+@Configuration
+@ComponentScan(basePackages="example.scannable",
+ useDefaultFilters=false,
+ includeFilters={
+ @Filter(CustomStereotype.class),
+ @Filter(CustomComponent.class)
+ }
+ )
+class ComponentScanWithMultipleAnnotationIncludeFilters1 { }
+
+@Configuration
+@ComponentScan(basePackages="example.scannable",
+ useDefaultFilters=false,
+ includeFilters=@Filter({CustomStereotype.class, CustomComponent.class})
+ )
+class ComponentScanWithMultipleAnnotationIncludeFilters2 { }
+
@Configuration
@ComponentScan(
value="example.scannable",