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:
parent
df7bda5637
commit
28ff473091
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue