Spring 启动过程 解析配置类

博文目录


内容总结

refresh - invokeBeanFactoryPostProcessor

Spring 启动过程中, 在 refresh 的 invokeBeanFactoryPostProcessor 阶段, Spring 会获取到系统中所有的 BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessor 组件, 两者的关系与功能如下

  • BeanFactoryPostProcessor
    • postProcessBeanFactory, 可以从 BeanFactory 中获取并修改 BeanDefinition
  • BeanDefinitionRegistryPostProcessor, 是 BeanFactoryPostProcessor 的子接口, 拥有其父接口的能力
    • postProcessBeanFactory, 可以从 BeanFactory 中获取并修改 BeanDefinition
    • postProcessBeanDefinitionRegistry, 可以向 BeanFactory 中注册 BeanDefinition

Spring 在 invokeBeanFactoryPostProcessor 阶段, 会将这两种组件分组并排序, 先执行所有 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法, 再执行所有 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法

在这些组件中, 有一个 ConfigurationClassPostProcessor, 负责配置类的扫描与解析工作, 它实现了 BeanDefinitionRegistryPostProcessor 接口, 即同时实现了 postProcessBeanDefinitionRegistry 与 postProcessBeanFactory 两个方法, postProcessBeanDefinitionRegistry 方法内就会做配置类的解析, 扫描, 注册 BeanDefinition 等工作

ConfigurationClassPostProcessor - postProcessBeanDefinitionRegistry

有注册 BeanDefinition 的能力, 大致流程

  • 首先获取到所有 BeanDefinition 的 Names, 大致有如下内容
    • new AnnotationConfigApplicationContext 时传入的配置类, 在内部被注册
    • new AnnotatedBeanDefinitionReader 时注册的一些基础架构组件
    • 因为还没有开始扫描并注册自定义 BeanDefinition, 所以这里不会包含这些
  • 遍历并获取对应的 BeanDefinition, 根据缓存判断是否是已经解析过的配置类
    • 如果该 BeanDefinition 有 configurationClass 属性, 则说明该 BeanDefinition 是一个配置类, 且已经被解析过了, 不需要再次解析
  • 如果没有解析过, 则判断该 BeanDefinition 是否符合配置类的定义, 符合配置类定义的会被添加到候选项列表中
  • 如果候选列表是空的, 说明没有找到配置类, 该方法终止
  • 如果有找到候选项, 排序, 实例化 BeanNameGenerator 和 Environment
  • 实例化一个配置类解析器 ConfigurationClassParser
  • 使用配置类解析器来解析配置类 (parse 方法), 如果解析的过程中发现了新的其他配置类, 则递归解析之 (parse 方法)
    • 注意: 解析是生成 BeanDefinition, 并不会生成 Bean

什么是配置类?

ConfigurationClassUtils.checkConfigurationClassCandidate

  • 有 @Configuration 注解, 且注解的 proxyBeanMethods 属性的值为 true, 则该类是配置类, 且是 FULL 类型的
  • 有 @Configuration 注解, 但注解的 proxyBeanMethods 属性的值为 false, 则该类是配置类, 但是是 LITE 类型的
  • 没有 @Configuration 注解, 且类不是接口, 且满足下面两个条件中的任意一个, 同样是配置类, 是 LITE 类型的
    • 类上有 @Component, @ComponentScan, @Import, @ImportResource 中的任意一个
    • 类里有 @Bean 注解标注的方法
  • 在确定配置类是 FULL 或 LITE 之后, 都会在对应的 BeanDefinition 中设置对应标记

解析配置类 ConfigurationClassParser - parse

配置类解析器, 用来解析配置类

部分工具用法说明

  • @Conditional, 可以传入一个实现 Condition 接口的类的数组 (多个条件), 该接口有一个 matches 方法, 返回 true 表示该条件符合, 该注解里所有条件都返回 true, 累才会被解析并注册. 该注解可以用在类上或方法上
    • @ConditionalOnXxxx, Springboot 里面的这一堆制动配置条件注解就是依赖 @Conditional 注解实现的
  • @Component, 如果一个类上有该注解及其其衍生注解, 如 @Controller, @Service, @Repository 等, 则 Spring 扫描过程中, 该类会被扫描成为 BeanDefinition, 最终被创建为 Bean
  • @ComponentScan, Spring 会扫描通过该注解指定的包及其子包, 并查找带有@Component 注解及其其衍生注解, 如 @Controller, @Service, @Repository 等的类, 将其注册成为 BeanDefinition, 最终被创建为 Bean
  • @ComponentScans, 里面可以放多个 @ComponentScan, 每一个子项都将依次被扫描
  • @Import, 通过该注解将类的数组引入 Spring, 和 @Bean 有部分功能重叠
    • 如果被引入的类是 ImportSelector, 该接口定义了 selectImports 方法, 返回的是一组全限定类名数组, 这些类最终被注册成为 BeanDefinition, 而实现 ImportSelector 的类则不会被注册成为 BeanDefinition
      • 如果被引入的类是 DeferredImportSelector, 延迟导入, ImportSelector 在解析配置类的时候就顺带处理掉了, 而 DeferredImportSelector 在解析配置类时只是将其加入到一个容器中, 等到这一波所有的配置类都解析完成后才会处理
    • 如果被引入的类是 ImportBeanDefinitionRegistrar, 该接口可以向 BeanFactory 注册 BeanDefinition, 定义了两个重载的 registerBeanDefinitions 方法, 一个需要指定 BeanName, 一个则使用名称生成器
    • 如果被引入的类不属于上述情况, 则该类被注册成为 BeanDefinition, BeanName 是全限定类名, 而通过 @Bean 注解的方法注册的 BeanDefinition, BeanName 是方法名
  • @ImportResource, 用于导入 XML 配置文件
  • @PropertySource, 用于让 Spring 加载特定的属性配置文件到 Environment, 通过 @Value 获取并使用之. 和 @ConfigurationProperties 组合使用, 可以将属性文件与一个类绑定, 将属性文件中的变量值注入到该类的成员变量中
  • @PropertySources, 里面可以放多个 @PropertySource

大致流程

配置类解析器, 根据参数不同提供了多种重载的解析方法 parse

  • 判断配置类上是否有 @Conditional 注解, 有点话, 传入的所有条件都满足才会解析该配置类, 否则或跳过该配置类的解析
  • 递归解析配置类及其所有层级的父类
    • 判断配置类上是否存在 @Componemt 注解, 有的话则解析该配置类的内部类是否符合配置类定义, 符合的话则 解析该配置类
    • 判断配置类上是否存在 @PropertySource 注解引入了某个配置文件, 有的话将其内容添加到 Environment 中
    • 判断配置类上是否存在 @ComponentScan 注解, 有的话则扫描并注册 BeanDefinition, 检查扫描到的 BeanDefinition 中是否有符合配置类定义的, 复合的话则 解析该配置类. 解析配置类过程中唯一一个会注册 BeanDefinition 的过程
    • 判断配置类上是否存在 @Import 注解, 有的话则获取其导入的类, 调用 processImports 方法处理, 判断导入的类的类型
      • 如果导入的类是 ImportSelector, 实例化导入的这个类
        • 如果同时是一个 DeferredImportSelector, 延迟, 将该实例强转并加入到配置类解析器的 deferredImportSelectors 属性中, 等到这一波所有的配置类都解析完成后才会处理
        • 如果只是一个 ImportSelector, 调用 selectImports 方法拿到二次导入的全部的类, 递归调用 processImports 方法来解析二次导入的全部的类
      • 如果导入的类是 ImportBeanDefinitionRegistrar (有注册 BeanDefinition 的能力), 实例化该导入的类, 然后把之添加到配置类的 importBeanDefinitionRegistrars 属性里面, 待后续处理
      • 如果导入的类不是上述两种类型, 当做配置类进行解析
    • 判断配置类上是否存在 @ImportResource 注解, 将一个 XML 配置文件, 添加到配置类的 importedResources 属性中, 待后续处理
    • 判断配置类中是否存在 @Bean 注解的方法, 找到并添加到配置类的 beanMethods 属性中, 待后续处理
    • 判断配置类实现的接口中是否有默认的 @Bean 注解的方法, 找到并添加到配置类的 beanMethods 属性中, 待后续处理
    • 如果配置类有父类则获取并返回父类, 没有父类则返回 null, 把父类当做配置类去解析

BeanDefinition 覆盖

  • 两个不同类型但指定了相同 BeanName 的 @Component 配置, 不会覆盖而是直接报错, BeanDefinition 冲突
  • 两个 @Bean 方法, 但是有一个有参数有一个无参数, 不会报错, 而是会走推断构造方法类似逻辑, 找参数多的那个来调用. 当解析到第二个的时候, 直接返回中止流程
  • @Bean 和 @Component, 比如各定义了一个 UserService, @Bean 生成的 BeanDefinition 叫做 ConfigurationClassBeanDefinition, @Component 被扫描到生成的 BeanDefinition 叫做 ScannedGenericBeanDefinition, 发生重复时候, 会判断类型, @Component 会先被处理, @Bean 后处理, 后面的会生成新的 BeanDefinition 并覆盖前面的, 然后重新注册到容器中. 注册的时候会判断是否存在, 是否允许覆盖, 默认允许覆盖. 所以, 如果某个类同时存在 @Bean 和 @Component 两种配置, 通常是以 @Bean 的配置为准的, 包括其 @Lazy 和 @Scope 等信息

ConfigurationClassPostProcessor - postProcessBeanFactory

有修改 BeanDefinition 的能力, 在这个阶段, 所有的 BeanDefinition 都已经被扫描并注册, 但是还没有生成 Bean

@Configuration(proxyBeanMethods=true)

配置类注解 @Configuration 有什么用? 只有加 @Configuration@Configuration(proxyBeanMethods=true) (proxyBeanMethods 默认为 true) 的配置类叫做 FULL 配置类, 其他的配置类都叫做 LITE 配置类. FULL 配置类在 BeanFactoryPostProcessor 的 postProcessBeanFactory 阶段被做强化处理, 内部的 @Bean 方法被代理, 每次调用都会从单例池中获取单例 Bean, 不然每次调用都会生成不同的对象 (创建 Bean 的时候也会调用 @Bean 方法)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值