--- layout: post title: Spring源码学习 - Scanner的工作原理 category: 技术 tags: Spring Java keywords: Spring Java genBeanName description: Scanner的工作原理解析 modify: 2019-11-01 published: true --- Spring 自带的类扫描器为`ClassPathBeanDefinitionScanner`,在使用注解`@ComponentScan`,或者xml 配置``时都会调用该扫描器扫描指定的目标类。 `ClassPathBeanDefinitionScanner`最重要的方法是`doScan`,通过该方法完成 BeanDefinition 的生成和注册, 在扫描时会配置默认的过滤器用于扫描,比如`@Component`,`@ManagedBean`,`@Named`。 ## 扫描流程 扫描并注册流程 1. 解析 basePackages,将其分隔为数组,然后供`doScan`方法调用 2. 在`scanCandidateComponents`中将其 basePackage 转换为 Class 路径,`classpath*:/cn/followtry/**/*.class` 3. 通过默认的资源加载器,也就是类加载器`ClassLoader`解析指定的路径上的资源。 4. 判断资源是否可读,即是否存在。存在则创建元数据读入器`MetadataReader`。 5. 判断是否是候选的组件。先判断是否在排除的过滤器集合中,再判断是否在包含的过滤器集合中,对在包含过滤器中的组件还需要判断是否是匹配上的Condition。**可见排除(exclude),的级别比包含(include)高** 6. 对于判断为候选的组件,会生成`ScannedGenericBeanDefinition`作为转换为`BeanDefinition`的载体。 7. 将Resource 信息设置进`ScannedGenericBeanDefinition`,然后再判断其是否为候选组件,如果是则添加到候选组件列表中。判断条件为该 Bean通过元数据判断是独立的,并且是具体类 或者 抽象类但是有`@Lookup`注解。参考`ClassPathScanningCandidateComponentProvider#isCandidateComponent` 8. 对于`AbstractBeanDefinition`类型的`BeanDefinition`设置默认值和`Autowire`的候选值。参考`ClassPathBeanDefinitionScanner#postProcessBeanDefinition` 9. 对于`AnnotatedBeanDefinition`类型的`BeanDefinition`,会处理一些公共类型的注解。如`@Lazy`,`@Primary`,`@DependsOn`,`@Role`,`@Description`等注解,会将这些注解的 value 数据补充进`BeanDefinition`。参考方法:`AnnotationConfigUtils#processCommonDefinitionAnnotations` 10. 检查候选者信息,如果检查通过则新注册 Bean。判断条件为:1.该 beanName 是否已经注册。2.对于已注册的情况,检查新老 bean 是否兼容(类型相同,source 相同,两个 bean 定义相同)。如果不兼容会抛出异常。参考`ClassPathBeanDefinitionScanner#checkCandidate` 11. 构建持有器`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 设置参数。 如设置 1. useDefaultFilters : 是否使用默认过滤器 2. nameGenerator : 指定的命名生成器 3. scopedProxy: 代理范围 4. scopeResolver : 代理解析器 5. resourcePattern : 资源模式 6. includeFilters:包含的过滤器 7. excludeFilters:排除的过滤器 8. lazyInit: 懒加载 9. basePackages : 基础扫描包路径 10. 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`来完成``的解析。 此处的`Parser`的查找需要命名空间处理器`NamespaceHandler`的协助,Spring会通过解析命名空间和`META-INF/spring.handlers`中配置的关联关系找到`ContextNamespaceHandler`,然后通过解析xml配置中的解析器名称,就可以找到已经初始化好的解析器。这样就可以调用解析器的解析方法了。