1. 概述
在之前的教程中,我们已经了解了 Spring 组件扫描的基础知识。
本文将深入探讨 @ComponentScan
注解提供的多种过滤机制。通过这些过滤器,我们可以更精细地控制哪些类应该被注册为 Spring Bean,哪些应该被排除。
2. @ComponentScan 过滤机制
默认情况下,被 @Component
、@Repository
、@Service
、@Controller
注解标记的类会被自动注册为 Spring Bean。此外,任何被 @Component
注解的自定义注解所标记的类,也会被纳入扫描范围。
我们可以通过 @ComponentScan
的 includeFilters
和 excludeFilters
参数来扩展这一行为,实现更灵活的组件发现策略。
✅ 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 过滤器,排除类名以 L
或 C
开头的类:
@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。