Spring Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

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

By default, classes annotated with @Component, @Repository, @Service, @Controller are registered as Spring beans. The same goes for classes annotated with a custom annotation that is annotated with @Component. We can extend this behavior by using includeFilters and excludeFilters parameters of the @ComponentScan annotation.

There are five types of filters available for ComponentScan.Filter :

  • ANNOTATION
  • ASSIGNABLE_TYPE
  • ASPECTJ
  • REGEX
  • CUSTOM

We'll see these in detail in the next sections.

We should note that all these filters can include or exclude classes from scanning. For simplicity in our examples, we'll only include classes.

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

4. 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));
}

5. 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 Loin class:

public class Loin { }

Let's use FilterType.REGEX which instructs Spring to scan classes which 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));
}

6. 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.baeldung.componentscan.filter.aspectj.* "
  + "&& !(com.baeldung.componentscan.filter.aspectj.L* "
  + "|| com.baeldung.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"));
}

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

8. Summary

In this tutorial, we introduced filters associated with @ComponentScan.

As usual, the complete code is available over on GitHub.

Spring bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!