1. 前情提要
In an earlier tutorial, we learned about
the basics of Spring component scans.
In this write-up, we’ll see the different types of filter options available with the @ComponentScan annotation.
2. @ComponentScan Filter
There are five types of filters available for
ComponentScan.Filter
-
FilterType.ANNOTATION
-
FilterType.ASSIGNABLE_TYPE
-
FilterType.ASPECTJ
-
FilterType.REGEX
-
FilterType.CUSTOM
We should note that all these filters can
include
or
exclude
classes from scanning by using
includeFilters
and
excludeFilters
parameters of the @ComponentScan annotation.
For simplicity in our examples, we’ll only include classes.
2.1 FilterType.ANNOTATION
The
ANNOTATION
filter type
includes
or
excludes
classes in the component scans which are
marked with given annotations
.
Let’s say, for example, that we have an @Animal annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Animal { }
Now, let’s define an Elephant class which uses @Animal:
@Animal
public class Elephant { }
Finally, let’s use the
FilterType.ANNOTATION
to tell Spring to scan for @Animal-annotated classes:
@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Animal.class))
public class ComponentScanAnnotationFilterApp { }
As we can see, the scanner picks up our Elephant just fine:
@Test
public void whenAnnotationFilterIsUsed_thenComponentScanShouldRegisterBeanAnnotatedWithAnimalAnootation() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanAnnotationFilterApp.class);
List<String> beans = Arrays.stream(applicationContext.getBeanDefinitionNames())
.filter(bean -> !bean.contains("org.springframework") && !bean.contains("componentScanAnnotationFilterApp"))
.collect(Collectors.toList());
assertThat(beans.size(), equalTo(1));
assertThat(beans.get(0), equalTo("elephant"));
}
2.2 FilterType.ASSIGNABLE_TYPE
The
ASSIGNABLE_TYPE
filters all classes during the component scan that
either extend the class or implement the interface of the specified type
.
First, let’s declare the Animal interface:
public interface Animal { }
And again, let’s declare our Elephant class, this time implementing Animal interface:
public class Elephant implements Animal { }
Let’s declare our Cat class that also implementing Animal:
public class Cat implements Animal { }
Now, let’s use
ASSIGNABLE_TYPE
to guide Spring to scan for Animal-implementing classes:
@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = Animal.class))
public class ComponentScanAssignableTypeFilterApp { }
And we’ll see that both Cat and Elephant get scanned:
@Test
public void whenAssignableTypeFilterIsUsed_thenComponentScanShouldRegisterBean() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanAssignableTypeFilterApp.class);
List<String> beans = Arrays.stream(applicationContext.getBeanDefinitionNames())
.filter(bean -> !bean.contains("org.springframework") && !bean.contains("componentScanAssignableTypeFilterApp"))
.collect(Collectors.toList());
assertThat(beans.size(), equalTo(2));
assertThat(beans.contains("cat"), equalTo(true));
assertThat(beans.contains("elephant"), equalTo(true));
}
2.3 FilterType.REGEX
The
REGEX
filter checks if
the class name matching a given regex pattern
.
FilterType.REGEX
checks
both simple and fully-qualified class names.
Once again, let’s declare our Elephant class. This time not implementing any interface or annotated with any annotation:
public class Elephant { }
Let’s declare one more class Cat:
public class Cat { }
Now, let’s declare Lion class:
public class Lion { }
Let’s use
FilterType.REGEX
which instructs Spring to scan classes that match regex
.*[nt]
. Our regex expression evaluates everything containing nt:
@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*[nt]"))
public class ComponentScanRegexFilterApp { }
This time in our test, we’ll see that Spring scans the Elephant, but not the Lion:
@Test
public void whenRegexFilterIsUsed_thenComponentScanShouldRegisterBeanMatchingRegex() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanRegexFilterApp.class);
List<String> beans = Arrays.stream(applicationContext.getBeanDefinitionNames())
.filter(bean -> !bean.contains("org.springframework") && !bean.contains("componentScanRegexFilterApp"))
.collect(Collectors.toList());
assertThat(beans.size(), equalTo(1));
assertThat(beans.contains("elephant"), equalTo(true));
}
2.4 FilterType.ASPECTJ
When we want to use expressions to
pick out a complex subset of classes,
we need to use the
FilterType.ASPECTJ
.
For this use case, we can reuse the same three classes as in the previous section.
Let’s use
FilterType.ASPECTJ
to direct Spring to scan classes that match our AspectJ expression:
@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASPECTJ,
pattern = "com.componentscan.filter.aspectj.* && !(com.componentscan.filter.aspectj.L* || com.componentscan.filter.aspectj.C*)"))
public class ComponentScanAspectJFilterApp { }
While a bit complex, our logic here wants beans that start with neither “L” nor “C” in their class name, so that leaves us with Elephants again:
@Test
public void whenAspectJFilterIsUsed_thenComponentScanShouldRegisterBeanMatchingAspectJCreteria() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanAspectJFilterApp.class);
List<String> beans = Arrays.stream(applicationContext.getBeanDefinitionNames())
.filter(bean -> !bean.contains("org.springframework") && !bean.contains("componentScanAspectJFilterApp"))
.collect(Collectors.toList());
assertThat(beans.size(), equalTo(1));
assertThat(beans.get(0), equalTo("elephant"));
}
2.5 FilterType.CUSTOM
If none of the above filter types meet our requirement then we can also create
a custom filter type
. For example, let’s say we only want to scan classes whose name is five characters or shorter.
To create a custom filter, we need to implement the
org.springframework.core.type.filter.TypeFilter
:
public class ComponentScanCustomFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String fullyQualifiedName = classMetadata.getClassName();
String className = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf(".") + 1);
return className.length() > 5 ? true : false;
}
}
Let’s use
FilterType.CUSTOM
which conveys Spring to scan classes using our custom filter ComponentScanCustomFilter:
@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = ComponentScanCustomFilter.class))
public class ComponentScanCustomFilterApp { }
Now it’s time to see test case of our custom filter ComponentScanCustomFilter:
@Test
public void whenCustomFilterIsUsed_thenComponentScanShouldRegisterBeanMatchingCustomFilter() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanCustomFilterApp.class);
List<String> beans = Arrays.stream(applicationContext.getBeanDefinitionNames())
.filter(bean -> !bean.contains("org.springframework") && !bean.contains("componentScanCustomFilterApp") && !bean.contains("componentScanCustomFilter"))
.collect(Collectors.toList());
assertThat(beans.size(), equalTo(1));
assertThat(beans.get(0), equalTo("elephant"));
}