在继续实现WinterApplicaitonContext之前,梳理一下Spring原生BFPP的机制。主要是注解处理核心ConfigurationClassPostProcessor。

在注解驱动的Spring中,这是唯一一个起作用的BeanFactoryPostProcessor。当ApplicationContext调用refresh方法进行刷新时,在invokeBeanFactoryPostProcessors时,会调用所有的BFPP的postProcessBeanDefinitionRegistry方法来对容器中的BeanDefinition进行处理。

注:BFPP有两种:

  • 一种是实现了BeanDefinitionRegistryPostProcessor接口的。它们是高级BFPP,用来注册BeanDefinition的,所以要优先执行,ConfigurationClassPostProcessor就属于这一种。
  • 一种是普通的,实现了BeanFactoryPostProcessor接口。

CCPP的postProcessBeanDefinitionRegistry方法中会调用processConfigBeanDefinitions来处理,所以主要是看这个方法的流程。

processConfigBeanDefinitions

  1. 首先它会拿到所有已经注册进容器的Bean的name列表,把它们作为候选者
  2. 遍历所有的beanName,用ConfigurationClassUtils.checkConfigurationClassCandidate筛选出真正的配置类(就是在容器构造时传入的我们自定义的配置类)
    1. 先检查是否解析过了,解析过的BeanDefinition是AnnotatedBeanDefinition。前面说过AC在构造的时候,会调用register方法将配置类注册进容器。那时候就会将配置类解析成AnnotatedBeanDefinition。如果已经解析过了,就直接拿出BD中的metadata。
    2. 会检查当前的beanName是否为框架中的类(BeanFactoryPostProcessor、BeanPostProcessor、AopInfrastructureBean、EventListenerFactory)。是的话直接跳过,这些是不处理的。
    3. 前面两个检查都没命中,会用一个兜底直接从bean中取出metadata
    4. 从metadata中提取出@Configuration注解的值,有两个proxyBeanMethods(默认为true)和value。之后会将配置类设置为full模式。
    5. 这样check方法就执行完毕了。主要功能就是看当前类是否满足配置类的要求以及解析下配置类的模式。

配置类有两种模式,同时满足下面两种模式的才能是配置类:

  1. Full模式,有@Configuration并且proxyBeanMethods=true。会为配置类生成一个CGLIB代理对象。
  2. Lite模式,Spring5.2后,大部分配置类都用的lite模式
    1. 有@Configuration但proxyBeanMethods=false。不会做CGLIB增强。
    2. 没有@Configuration,但是有@Component、@Service、@Repository、@Controller / @RestController、@ComponentScan、@Import、@ImportResource、@Bean
  1. check成功后,会将该配置类加到configCandidates这个列表中,之后创建一个ConfigurationClassParser开始解析,parse方法通过doProcessConfigurationClass完成解析,下面是该方法的流程。ConfigurationClassParser解析后得到一个配置类的列表(用ConfigurationClass去存储配置类信息)。
    1. 首先会检查类上有没有@Component及其衍生注解,有的话就用processMemberClasses递归解析其内部类。满足Lite模式的内部类会被视为配置类。之后这个配置类会重新走一遍parse方法进行解析。(自己实现时会省略这个递归解析,就不再细分析了)
    2. 处理@PropertySource注解,用来指定配置文件的,比如xx.properties的
    3. 处理@ComponentScan注解,会用ComponentScanAnnotationParser的parse方法去处理。parse方法接收两个参数:注解元数据的AnnotationAttributes(里面是注解各个值,比如basePackage、includeFilter)和配置类全限定名。具体解析过程如下:
  • 首先会创建一个ClassPathBeanDefinitionScanner,这个类在AC构造方法中也提到过,用来扫描包的工具。
  • Scanner根据@ComponentScan的元数据去扫描指定的路径,找出所有符合规则的类(例如带有 @Component、@Service、@Controller 等注解的类),将它们封装成 BeanDefinition 对象,并直接注册到 Spring 容器中。
    • 扫描的时候用的是doScan方法。首先它把basePackage的包名转成资源路径名,例如 classpath*:com/example/**/*.class,找到路径所有的.class文件。这里会用Resource对文件进行抽象。之后用ASM 技术读取这些类的元数据。
    • 前面提到过说Scanner初始化的时候会设置一个白名单,includeFilters这个属性,用于指定哪些注解标记的类会被扫描为Bean。默认是@Component注解。(@Service、@Controller、@Resource、@Configuration这些属于Componet的派生注解,也会生效。)
4. 处理@Import注解,用于导入其他的配置类或组件。使用processImports方法,具体解析过程如下:
  • 首先会拿到Import注解中的配置类class,进行遍历
  • 判断这个类是否实现了ImportSelector接口。如果实现了,会通过反射实例化ImportSelector对象。紧接着调用它的 selectImports() 方法。这个方法会返回一个字符串数组,里面包含了需要动态导入的全限定类名。之后对这些类递归调用processImports进行处理。
  • 判断这个类是否实现了ImportBeanDefinitionRegistrar接口。
  • 都没有实现就当做是@Configuration注解的配置类,按照3.3进行解析
5. 处理@ImportResource,兼容旧时代的 XML 配置,将指定的 XML 文件中的 Bean 定义也加载进来
6. 处理@Bean标注的方法,扫描当前配置类中所有被 @Bean 标注的方法。会将这些方法记录到当前配置类的beanMethods中。
7. 至此,对于类上注解的解析就完成了
  1. 解析过程中,所有解析完的配置类会放到ConfigurationClasses这个列表中。
  2. 会对比当前BF中的BeanDefinition个数和解析前是否一致。如果多了说明在解析过程中,解析出了新的配置类。那就用parser在解析新出现的配置类。直到不再出现新的配置类。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐