Spring - @ComponentScan custom filtering

[Updated: May 2, 2017, Created: Mar 25, 2017]

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

Output

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

Output

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

Output

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

Output

filterTypeCustomExample
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 Project

Dependencies and Technologies Used :

  • spring-context 4.3.7.RELEASE: Spring Context.
  • JDK 1.8
  • Maven 3.3.9

Component Scan Filtering Examples Select All Download
  • comp-scan-filtering
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also