Spring @ComponentScan – Filter Types

  • Post author:
  • Post category:其他




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"));
}

参考:


Spring @ComponentScan – Filter Types