«1. Обзор

В предыдущем уроке мы узнали об основах сканирования компонентов Spring.

В этой статье мы увидим различные типы параметров фильтрации, доступные с аннотацией @ComponentScan.

2. Фильтр @ComponentScan

По умолчанию классы, аннотированные с помощью @Component, @Repository, @Service, @Controller, регистрируются как компоненты Spring. То же самое касается классов, аннотированных пользовательской аннотацией, которая аннотирована с помощью @Component. Мы можем расширить это поведение, используя параметры includeFilters и excludeFilters аннотации @ComponentScan.

Для ComponentScan.Filter доступно пять типов фильтров:

    АННОТАЦИЯ ASSIGNABLE_TYPE ASPECTJ REGEX CUSTOM

Мы подробно рассмотрим их в следующих разделах.

Стоит отметить, что все эти фильтры могут включать или исключать классы из сканирования. Для простоты в наших примерах мы будем включать только классы.

3. FilterType.ANNOTATION

Тип фильтра ANNOTATION включает или исключает классы в сканах компонентов, которые отмечены заданными аннотациями.

Предположим, например, что у нас есть аннотация @Animal:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Animal { }

Теперь давайте определим класс Elephant, который использует @Animal:

@Animal
public class Elephant { }

Наконец, давайте используем FilterType.ANNOTATION для скажите Spring сканировать классы с аннотациями @Animal:

@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,
        classes = Animal.class))
public class ComponentScanAnnotationFilterApp { }

Как мы видим, сканер прекрасно распознает нашего Elephant:

@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

ASSIGNABLE_TYPE фильтрует все классы во время сканирования компонентов, которые либо расширяют класс, либо реализуют интерфейс указанного типа.

Во-первых, давайте объявим интерфейс Animal:

public interface Animal { }

И снова давайте объявим наш класс Elephant, на этот раз реализующий интерфейс Animal:

public class Elephant implements Animal { }

Давайте объявим наш класс Cat, который также реализует Animal: ~~ ~

public class Cat implements Animal { }

Теперь давайте воспользуемся ASSIGNABLE_TYPE, чтобы указать Spring для сканирования классов, реализующих Animal:

@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
        classes = Animal.class))
public class ComponentScanAssignableTypeFilterApp { }

И мы увидим, что сканируются и Cat, и Elephant:

@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

Фильтр REGEX проверяет, соответствует ли имя класса заданному шаблону регулярного выражения. FilterType.REGEX проверяет как простые, так и полные имена классов.

Еще раз объявим наш класс Elephant. На этот раз без реализации какого-либо интерфейса или аннотаций:

public class Elephant { }

Объявим еще один класс Cat:

public class Cat { }

Теперь давайте объявим класс Loin:

public class Loin { }

Давайте воспользуемся FilterType.REGEX, который инструктирует Spring сканировать классы, соответствующие регулярному выражению .*[nt]. Наше регулярное выражение оценивает все, что содержит nt:

@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.REGEX,
        pattern = ".*[nt]"))
public class ComponentScanRegexFilterApp { }

На этот раз в нашем тесте мы увидим, что Spring сканирует Elephant, но не 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

Когда мы хотим использовать выражения для выбора сложного подмножества классов, нам нужно использовать FilterType ASPECTJ.

В этом случае мы можем повторно использовать те же три класса, что и в предыдущем разделе.

Давайте воспользуемся FilterType.ASPECTJ, чтобы указать Spring сканировать классы, соответствующие нашему выражению AspectJ: их имя класса, так что мы снова остаемся со слонами:

@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 { }

7. FilterType.CUSTOM

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

Если ни один из вышеперечисленных типов фильтров не соответствует нашим требованиям, мы также можем создать собственный тип фильтра. Например, предположим, что мы хотим сканировать только те классы, имя которых не превышает пяти символов.

Чтобы создать собственный фильтр, нам нужно реализовать org.springframework.core.type.filter.TypeFilter:

Давайте используем FilterType.CUSTOM, который передает Spring для сканирования классов с использованием нашего пользовательского фильтра ComponentScanCustomFilter: ~ ~~

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

Теперь пришло время посмотреть пример нашего пользовательского фильтра ComponentScanCustomFilter:

@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM,
  classes = ComponentScanCustomFilter.class))
public class ComponentScanCustomFilterApp { }

8. Резюме

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

В этом руководстве мы представили фильтры, связанные с @ComponentScan.

Как обычно, полный код доступен на GitHub.

«