From 28ff473091822195a4c6e00d7eaba2c09567d700 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Tue, 6 Dec 2011 12:18:10 +0000 Subject: [PATCH] Allow multiple @Filter 'value' args for concision Prior to this change, to specify two or more annotation include/exclude filters, one would declare @ComponentScan as follows: @ComponentScan(basePackages="example.scannable", useDefaultFilters=false, includeFilters={ @Filter(MyStereotype.class), @Filter(MyComponent.class) }) This was because @Filter's 'value' attribute accepted exactly one argument. Now, any given @Filter may accept one or more value arguments, allowing for more concise @ComponentScan declarations: @ComponentScan(basePackages="example.scannable", useDefaultFilters=false, includeFilters=@Filter({MyStereotype.class, MyComponent.class})) Supplying multiple arguments in this way assumes that they are the same type of filter, e.g. ANNOTATION, ASSIGNABLE_TYPE, or CUSTOM. To declare multiple *different* types of filters, multiple @Filter annotations are still required, e.g.: @ComponentScan( includeFilters={ @Filter(type=ANNOTATION, value=MyStereotype.class), @Filter(type=ASSIGNABLE_TYPE, value={Foo.class, Bar.class}) }) Note that specifying zero arguments, e.g. @Filter({}) is nonsensical; it will have no effect on component scanning, but does not raise an error. Issue: SPR-8881 --- .../context/annotation/ComponentScan.java | 17 ++++--- .../ComponentScanAnnotationParser.java | 49 +++++++++++++------ ...mponentScanAnnotationIntegrationTests.java | 41 +++++++++++++++- 3 files changed, 84 insertions(+), 23 deletions(-) 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",