1. 概述

在之前的教程中,我们已经了解了 Spring 组件扫描的基础知识

本文将深入探讨 @ComponentScan 注解提供的多种过滤机制。通过这些过滤器,我们可以更精细地控制哪些类应该被注册为 Spring Bean,哪些应该被排除。

2. @ComponentScan 过滤机制

默认情况下,被 @Component@Repository@Service@Controller 注解标记的类会被自动注册为 Spring Bean。此外,任何被 @Component 注解的自定义注解所标记的类,也会被纳入扫描范围。

我们可以通过 @ComponentScanincludeFiltersexcludeFilters 参数来扩展这一行为,实现更灵活的组件发现策略。

ComponentScan.Filter 支持以下五种过滤类型:

  • ANNOTATION:基于注解进行过滤
  • ASSIGNABLE_TYPE:基于继承或实现关系
  • ASPECTJ:使用 AspectJ 表达式匹配
  • REGEX:正则表达式匹配类名
  • CUSTOM:自定义过滤逻辑

⚠️ 所有这些过滤器都可用于包含或排除类。为了简化示例,本文仅演示 includeFilters 的使用。

3. FilterType.ANNOTATION

该类型用于包含或排除带有指定注解的类。

假设我们定义了一个自定义注解 @Animal

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

接着创建一个使用该注解的 Elephant 类:

@Animal
public class Elephant { }

现在配置 @ComponentScan,让它只扫描带有 @Animal 注解的类:

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

测试验证:

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

✅ 测试通过,说明 Elephant 被成功注册为 Bean。

4. FilterType.ASSIGNABLE_TYPE

此类型用于包含所有继承指定类或实现指定接口的类。

定义 Animal 接口:

public interface Animal { }

两个实现类:

public class Elephant implements Animal { }
public class Cat implements Animal { }

配置扫描规则:

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

测试验证:

@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

使用正则表达式匹配类名(支持简单名和全限定名)。

定义三个类用于测试:

public class Elephant { }
public class Cat { }
public class Lion { }

配置正则过滤器,匹配类名中包含 nt 的类:

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

测试验证:

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

✅ 只有 Elephant 匹配正则表达式,被注册为 Bean。

6. FilterType.ASPECTJ

当需要复杂的匹配逻辑时,可以使用 AspectJ 表达式。

继续使用上述三个类,配置 AspectJ 过滤器,排除类名以 LC 开头的类:

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

测试验证:

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

Elephant 是唯一符合条件的类,被成功注册。

7. FilterType.CUSTOM

如果内置过滤器无法满足需求,可以自定义过滤逻辑。

例如,我们希望只扫描类名长度大于 5 的类:

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

⚠️ 注意:这里逻辑是 > 5 返回 true,即匹配类名长度大于 5 的类。Elephant 长度为 8,应被包含。

配置使用自定义过滤器:

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

测试验证:

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

✅ 自定义过滤器生效,只有 Elephant 被注册。

8. 总结

本文系统介绍了 @ComponentScan 支持的五种过滤类型:

  • ANNOTATION:基于注解筛选
  • ASSIGNABLE_TYPE:基于类型继承关系
  • REGEX:正则表达式灵活匹配
  • ASPECTJ:复杂表达式支持
  • CUSTOM:完全自定义逻辑

这些过滤器为组件扫描提供了强大的控制能力,尤其在大型项目中,能有效避免不必要的 Bean 注册,提升启动性能。

所有示例代码已上传至 GitHub 仓库:https://github.com/tech-tutorial/spring-boot-di


原始标题:Spring @ComponentScan - Filter Types