@ComponentScan , by default, look for the classes annotated with @Component, @Repository, @Service, @Controller, or any user defined annotation which has been meta-annotated with @Component. We can turn off this default behavior by using 'useDefaultFilters' element of @ComponentScan:
@ComponentScan(useDefaultFilters = false)
With or without useDefaultFilters = false, We may also want to supply our own custom filtering by using element 'includeFilters'
@ComponentScan(useDefaultFilters = false/true, includeFilters = {@ComponentScan.Filter{ ... })
Or we may want to provide 'excludeFilter':
@ComponentScan(useDefaultFilters = false/true, excludeFilters = {@ComponentScan.Filter{ ... })
@ComponentScan.Filter annotation
Following snippet shows the elements defined in nested @Filter annotation:
package org.springframework.context.annotation;
....
public @interface ComponentScan {
....
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
....
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
The most important element is FilterType enum which has five values: ANNOTATION, ASSIGNABLE_TYPE, ASPECTJ, REGEX and CUSTOM.
In this tutorial we will walk through examples to understand the usage of different FilterType.
FilterType.ASSIGNABLE_TYPE example
FilterType.ASSIGNABLE_TYPE together with Filter.classes or Filter.value can be used to specify assignable classes to be searched in scanning. The 'assignable classes' means, the scanning returns sub types as well (per Class#isAssignableFrom(..) definition)
Consider very simple beans:
public class MyBean1 {
}
public class MyBean2 {
}
public class MyBean3 {
}
public class MyBean4 extends MyBean3{
}
We don't have to use @Component annotation with above beans, because we are going to turn off default scanning in our example.
We have also created a Util class to reuse code:
public class Util {
public static void printBeanNames(ApplicationContext context){
String[] beanNames = context.getBeanDefinitionNames();
Arrays.stream(beanNames)
.filter(n -> !n.contains("springframework"))
.forEach(System.out::println);
}
}
Here's the filter usage:
@Configuration
@ComponentScan(useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE, classes = {MyBean1.class, MyBean3.class})})
public class FilterTypeAssignableExample {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(FilterTypeAssignableExample.class);
Util.printBeanNames(context);
}
} OutputfilterTypeAssignableExample myBean1 myBean3 myBean4
Output includes the name of MyBean4 as well, that's because My Bean3. class. is Assignable From( My Bean4. class) returns true.
Also 'filterTypeAssignableExample' is in the output, Why? because 'this' @Configuration class is registered as a bean too (not because of scanning, but because of the constructor Annotation Config Application Context( Filter Type Assignable Example. class) ).
Filer.pattern along with ASSIGNABLE_TYPE not supported
@Configuration
@ComponentScan(useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, pattern = ".*1")})
public class FilterTypeAssignableExample2 {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(FilterTypeAssignableExample2.class);
Util.printBeanNames(context);
}
}
Output
Caused by: java.lang.IllegalArgumentException: Filter type not supported with String pattern: ASSIGNABLE_TYPE
at org.springframework.context.annotation.ComponentScanAnnotationParser.typeFiltersFor(ComponentScanAnnotationParser.java:176)
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:100)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:282)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:244)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:197)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:166)
... 13 more
Filter.pattern can only be used with FilterType.REGEX (next example)
FilterType.REGEX example
Following regex pattern example will scan only beans classes ending with 1 or 2.
Note that we also have to exclude our FilterTypeAssignableExample2 (from last example) from being scanned because it has '2' at the end.
@Configuration
@ComponentScan(useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*[12]"),
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = FilterTypeAssignableExample2.class)
)
public class FilterTypeRegexExample {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(FilterTypeRegexExample.class);
Util.printBeanNames(context);
}
} OutputfilterTypeRegexExample myBean1 myBean2
Note that when specifying 'pattern' value along with the FilterType other than REGEX, we will have 'not supported' exception.
FilterType.ANNOTATION example
This is the default FilterType which causes the @Component annotation to be searched. We can specify our own custom annotation, in that case we have to specify 'classes' element as being our annotation class.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
@MyAnnotation
public class MyBean5 {
}
@Configuration
@ComponentScan(useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyAnnotation.class)})
public class FilterTypeAnnotationExample {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(FilterTypeAnnotationExample.class);
Util.printBeanNames(context);
}
} OutputfilterTypeAnnotationExample myBean5
In above output MyBean1, MyBean2 etc are not included even though they are in the same package.
FilterType.CUSTOM example
FilterType.CUSTOM can be used for a custom programmatic filtering of the scanned classes. In that case, we have to assign an implementation of TypeFilter to the Filter.classes element.
In following example, scanning will only target the bean classes which are implementing java.lang.Runnable interface.
public class MyTypeFilter implements TypeFilter {
private static final String RunnableName = Runnable.class.getName();
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String[] interfaceNames = classMetadata.getInterfaceNames();
if (Arrays.stream(interfaceNames).anyMatch(RunnableName::equals)) {
return true;
}
return false;
}
}
public class MyBean6 implements Runnable{
@Override
public void run() {
//todo
}
}
@Configuration
@ComponentScan(useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
)
public class FilterTypeCustomExample {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(FilterTypeCustomExample.class);
Util.printBeanNames(context);
}
} OutputfilterTypeCustomExample myBean6
In above output, other example beans are not included even though they are in the same package.
There's one more FilterType i.e. ASPECTJ which is outside of Spring core. This filter type matches the beans based on AspectJ type pattern expression.
Example ProjectDependencies and Technologies Used: - spring-context 6.1.2 (Spring Context)
Version Compatibility: 3.2.3.RELEASE - 6.1.2 Version compatibilities of spring-context with this example: Versions in green have been tested.
- JDK 17
- Maven 3.8.1
|