Spring 自带的类扫描器为ClassPathBeanDefinitionScanner,在使用注解@ComponentScan,或者xml 配置<context:component-scan/>时都会调用该扫描器扫描指定的目标类。
ClassPathBeanDefinitionScanner最重要的方法是doScan,通过该方法完成 BeanDefinition 的生成和注册,
在扫描时会配置默认的过滤器用于扫描,比如@Component,@ManagedBean,@Named。
扫描流程
扫描并注册流程
- 解析 basePackages,将其分隔为数组,然后供
doScan方法调用 - 在
scanCandidateComponents中将其 basePackage 转换为 Class 路径,classpath*:/cn/followtry/**/*.class - 通过默认的资源加载器,也就是类加载器
ClassLoader解析指定的路径上的资源。 - 判断资源是否可读,即是否存在。存在则创建元数据读入器
MetadataReader。 - 判断是否是候选的组件。先判断是否在排除的过滤器集合中,再判断是否在包含的过滤器集合中,对在包含过滤器中的组件还需要判断是否是匹配上的Condition。可见排除(exclude),的级别比包含(include)高
- 对于判断为候选的组件,会生成
ScannedGenericBeanDefinition作为转换为BeanDefinition的载体。 - 将Resource 信息设置进
ScannedGenericBeanDefinition,然后再判断其是否为候选组件,如果是则添加到候选组件列表中。判断条件为该 Bean通过元数据判断是独立的,并且是具体类 或者 抽象类但是有@Lookup注解。参考ClassPathScanningCandidateComponentProvider#isCandidateComponent - 对于
AbstractBeanDefinition类型的BeanDefinition设置默认值和Autowire的候选值。参考ClassPathBeanDefinitionScanner#postProcessBeanDefinition - 对于
AnnotatedBeanDefinition类型的BeanDefinition,会处理一些公共类型的注解。如@Lazy,@Primary,@DependsOn,@Role,@Description等注解,会将这些注解的 value 数据补充进BeanDefinition。参考方法:AnnotationConfigUtils#processCommonDefinitionAnnotations - 检查候选者信息,如果检查通过则新注册 Bean。判断条件为:1.该 beanName 是否已经注册。2.对于已注册的情况,检查新老 bean 是否兼容(类型相同,source 相同,两个 bean 定义相同)。如果不兼容会抛出异常。参考
ClassPathBeanDefinitionScanner#checkCandidate - 构建持有器
BeanDefinitionHolder,然后将其注册进 Spring 容器内。参考ClassPathBeanDefinitionScanner#registerBeanDefinition。
注意:扫描工具只会将候选的类筛选出来并注册 Bean 信息,但此时的 Bean 还未实例化。只是类的基本信息已经注册进 Spring 容器。
调用入口
sourceClass为参数类,如cn.followtry.boot.java.BriefSpringbootApplication
org.springframework.context.annotation.ComponentScanAnnotationParser
ComponentScanAnnotationParser是给注解@ComponentScan使用的,该类没有标记public修饰符,只能在当前包下使用。入口方法ComponentScanAnnotationParser#parse。刚方法内新实例化了ClassPathBeanDefinitionScanner。并根据注解@ComponentScan的设置值为 scan 设置参数。
如设置
- useDefaultFilters : 是否使用默认过滤器
- nameGenerator : 指定的命名生成器
- scopedProxy: 代理范围
- scopeResolver : 代理解析器
- resourcePattern : 资源模式
- includeFilters:包含的过滤器
- excludeFilters:排除的过滤器
- lazyInit: 懒加载
- basePackages : 基础扫描包路径
- basePackageClasses: 指令类,将其所属的包设作为 basePakcage
设置好了这些参数后,就可以调用扫描器的scanner.doScan方法扫描指定的资源了。而入口方法ComponentScanAnnotationParser#parse是在ConfigurationClassParser#doProcessConfigurationClass中调用的。即先判断并解析了@Configuration注解后,才会判断该注解标记的类上是否有@ComponentScan注解
basePackages 和 basePackageClasses 解析的包路径会合并去重。两个可以都设置也可以只设置一个。
org.springframework.context.annotation.ComponentScanBeanDefinitionParser
ComponentScanBeanDefinitionParser和ComponentScanAnnotationParser允许设置的参数基本相同。需要在方法configureScanner中创建 Scanner实例并为其设置好配置的属性值。
和注解方式的区别是:ComponentScanBeanDefinitionParser不可以设置basePackageClasses
在 Spring 初始化解析 xml 配置标签的时候,方法DefaultBeanDefinitionDocumentReader#parseBeanDefinitions中的delegate.parseCustomElement会调用ComponentScanAnnotationParser.parse来完成<context:component-scan/>的解析。
此处的Parser的查找需要命名空间处理器NamespaceHandler的协助,Spring会通过解析命名空间和META-INF/spring.handlers中配置的关联关系找到ContextNamespaceHandler,然后通过解析xml配置中的解析器名称,就可以找到已经初始化好的解析器。这样就可以调用解析器的解析方法了。
版权声明:本文由 在 2019年10月29日发表。本文采用CC BY-NC-SA 4.0许可协议,非商业转载请注明出处,不得用于商业目的。
文章题目及链接:《Spring源码学习 - Scanner的工作原理》