注解说明

@ComponentScan 注解是Spring 提供的对组件进行扫描的注解指令,通常与 @Configuration 注解一起使用,支持在类上重复标注

注解能做什么?

此注解支持以下重要特性:

  1. 自定义扫描的包范围
  2. 配置包含扫描组件的过滤器
  3. 配置排除组件的过滤器
  4. 是否使用默认过滤器
  5. 是否配置懒加载

源码分析

由于此注解通常与 @Configuration 注解一起使用,其解析的主要逻辑都是在 refresh 阶段的 invokeBeanFactoryPostProcessors 方法中,最终会定位到 ConfigurationClassParser#doProcessConfigurationClass() 方法中,在方法中可以看到对 ComponentScan 注解的操作逻辑了

对注解属性的解析

所有对属性的解析都在 ComponentScanAnnotationParser#parse() 方法中进行,此方法中会创建一个 ClassPathBeanDefinitionScanner 实例scanner,此实例初始化过程中会对默认的Filter进行加载

处理默认Filter主要是 JSR-250JSR-330 API 增加的注解

然后会把配置的 includeFilterexcludeFilter 设置到 scanner 中,接着会对 basePackage 扫描包属性进行解析配置,如果配置了就添加,没有配置,默认就使用 @ComponentScan 注解标注类所在的包路径。解析完注解属性后,就开始了真正的扫描操作

扫描操作

扫描操作由 ClassPathBeanDefinitionScanner#doScan() 方法进行处理,此方法会调用 findCandidateComponents() 方法在给定的包下查找符合条件的组件,查找的任务由 scanCandidateComponents() 进行,该方法调用 isCandidateComponent() 方法,根据解析的includeFilter和excludeFilter 来选择满足条件的组件

这样扫描到所有满足条件的Bean后,会接着对这些Bean进行parse操作,直到注解再无其它依赖

注册Bean

扫描完所有Bean后,会调用 checkCandidate() 方法进行检查,检查当前要注册的Bean是否与已经注册过的Bean的定义是否兼容,如果冲突会报 ConflictingBeanDefinitionException 。检查没问题后,会调用 registerBeanDefinition() 方法对Bean进行注册,这个过程就涉及到Spring其它的生命周期了(这个后续专门学习总结),对于@ComponentScan 组件来说已经完成了它的使命

总结

最后以注解解析过程中的涉及的核心类做一下总结

常见问题

调整包结构后一些@Bean配置扫描不到了

  1. 调用启动类的包路径为最外层,这样可以扫描当前包及子包的配置
  2. 默认只扫描当前标注注解的包,可以通过配置 basePackage 属性指定扫描路径,配置后默认的扫描路径就不生效了

如何将未配置Spring相关组件注解的Bean纳入Spring管理?

可以通过配置 ComponentScan的includeFilter 来实现

1
2
3
4
5
6
7
@ComponentScan(
        basePackages = {"cn.imcompany.bean.scan"},
        includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {SpecialComponent.class})
)

public class SpecialComponent {
}