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
This commit is contained in:
Chris Beams
2011-12-06 12:18:10 +00:00
parent df7bda5637
commit 28ff473091
3 changed files with 84 additions and 23 deletions

View File

@@ -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}.
* <p>When multiple classes are specified, OR logic is applied, e.g. "include
* types annotated with {@code @Foo} OR {@code @Bar}".
* <p>Specifying zero classes is permitted but will have no effect on component
* scanning.
*/
Class<?> value();
Class<?>[] value(); //doco
}
}

View File

@@ -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<String> basePackages = new ArrayList<String>();
@@ -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<Annotation> filterClass = (Class<Annotation>)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<TypeFilter> typeFiltersFor(Filter filterAnno) {
List<TypeFilter> typeFilters = new ArrayList<TypeFilter>();
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<Annotation> annoClass = (Class<Annotation>)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;
}
}