Spring Boot 的注解是如何生效的


在 Spring 中,@Configuration、@ComponentScan、@Bean、@Import 等注解的扫描、解析和 BeanDefinition 注册是一个分层处理的过程。下面我们以 @Configuration 类为例,结合代码流程详细说明其从扫描到注册的完整逻辑。

1. 整体流程概览

以下是核心步骤的流程图:

复制代码
1. 扫描候选配置类 → 2. 解析注解元数据 → 3. 注册 BeanDefinition

具体分为以下阶段:

  1. 扫描阶段 :通过 BeanDefinitionRegistry 获取所有候选配置类。
  2. 解析阶段 :使用 ConfigurationClassParser 解析注解(如 @ComponentScan、@Bean、@Import)。
  3. 注册阶段 :通过 ConfigurationClassBeanDefinitionReader 将解析结果注册为 BeanDefinition。

2. 详细步骤解析

2.1 扫描阶段:识别候选配置类

触发入口 :
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()
逻辑:

  1. 从 BeanDefinitionRegistry 获取所有已注册的 BeanDefinition 名称:

    java 复制代码
    String[] beanNames = registry.getBeanDefinitionNames();
  2. 遍历这些名称,检查对应的 BeanDefinition 是否是候选配置类:

    • 条件 :类上有 @Configuration、@Component、@ComponentScan、@Import、@ImportResource,或类中有 @Bean 方法。

    • 判断逻辑 :

      java 复制代码
      if (isFullConfigurationCandidate(beanDef) || isLiteConfigurationCandidate(beanDef)) {
          configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
      }
    • isFullConfigurationCandidate(beanDef):检查是否有 @Configuration 注解。

    • isLiteConfigurationCandidate(beanDef):检查是否有其他相关注解(如 @Component、@Bean 方法)。

关键点:

  • 扫描的输入是已注册的 BeanDefinition(可能来自 XML、Java Config 或自动扫描)。
  • 此时尚未解析注解内容,仅识别出需要进一步处理的候选类。

2.2 解析阶段:处理注解元数据

核心类 :ConfigurationClassParser
入口方法 :parse()
逻辑:递归解析每个候选配置类的注解。

(1) 解析 @ComponentScan
  • 作用 :扫描指定包路径下的 @Component 类(如 @Service、@Repository)。
  • 流程 :
    1. 获取 @ComponentScan 注解的 basePackages 或 basePackageClasses。

    2. 使用 ClassPathBeanDefinitionScanner 扫描类路径:

      java 复制代码
      scanner.scan(basePackages);
    3. 扫描到的类会被注册为新的 BeanDefinition(类型为 ScannedGenericBeanDefinition)。

  • 关键点 :
    • 扫描时使用 ASM 或反射读取类注解,避免提前加载类到 JVM。
    • 新注册的 BeanDefinition 可能也会被后续解析(如果它们也是配置类)。
(2) 解析 @Bean 方法
  • 作用 :将配置类中的 @Bean 方法转换为 BeanDefinition。

  • 流程 :

    1. 遍历配置类中的所有方法,筛选带 @Bean 注解的方法。
    2. 为每个 @Bean 方法生成一个 BeanDefinition:
      • 类型 :ConfigurationClassBeanDefinition。
      • 工厂方法 :设置为 @Bean 方法(通过 factoryMethodName 和 factoryBeanName 指定)。
      • 依赖 :解析 @Bean 方法的参数(按类型或 @Qualifier 注入)。
  • 示例 :

    java 复制代码
    @Configuration
    public class AppConfig {
        @Bean
        public DataSource dataSource() {
            return new HikariDataSource();
        }
    }
    • 生成的 BeanDefinition 会记录:factoryBeanName=appConfig, factoryMethodName=dataSource。
(3) 解析 @Import
  • 作用 :动态导入其他配置类或 BeanDefinition。
  • 三种处理方式 :
    1. 普通类 :直接注册为 BeanDefinition。

      java 复制代码
      @Import(OtherConfig.class)
    2. ImportSelector :通过编程方式选择要导入的类。

      java 复制代码
      @Import(MyImportSelector.class)
      • MyImportSelector 实现 selectImports() 方法,返回要导入的类名数组。
    3. ImportBeanDefinitionRegistrar :直接注册 BeanDefinition。

      java 复制代码
      @Import(MyRegistrar.class)
      • MyRegistrar 实现 registerBeanDefinitions() 方法,手动操作 BeanDefinitionRegistry。
(4) 处理父类与接口
  • 递归检查配置类的父类和接口,确保不遗漏任何 @Bean 方法或元注解。

2.3 注册阶段:加载 BeanDefinition

核心类 :ConfigurationClassBeanDefinitionReader
入口方法 :loadBeanDefinitions()
逻辑 :将解析结果(ConfigurationClass 对象)转换为 BeanDefinition 并注册到容器。

(1) 注册 @Import 的类
  • 普通类:通过 registry.registerBeanDefinition() 直接注册。
  • ImportBeanDefinitionRegistrar:调用其 registerBeanDefinitions() 方法。
(2) 注册 @Bean 方法
  • 为每个 @Bean 方法生成 BeanDefinition 并注册:

    java 复制代码
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }
(3) 处理嵌套配置类
  • 如果配置类内部有 @Configuration 静态嵌套类,递归处理。

3. 关键设计点

(1) 延迟加载与递归处理

  • 延迟加载 :@ComponentScan 扫描到的类可能也是配置类,需要递归解析。
  • 循环依赖处理 :Spring 通过提前暴露 BeanDefinition 解决配置类之间的循环引用。

(2) 元数据存储

  • ConfigurationClass 对象存储解析后的中间结果(如 @Bean 方法、@Import 类等)。
  • BeanDefinition 的 attribute 字段存储配置类的元信息(如 @Lazy、@Primary)。

(3) 性能优化

  • ASM 字节码分析:在扫描阶段避免加载类到 JVM。
  • 缓存 :解析结果缓存到 ConfigurationClass 中,避免重复处理。

4. 示例全流程

场景

java 复制代码
@Configuration
@ComponentScan("com.example.service")
@Import(OtherConfig.class)
public class AppConfig {
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

步骤

  1. 扫描阶段 :
    • 发现 AppConfig 是候选配置类(有 @Configuration)。
  2. 解析阶段 :
    • 解析 @ComponentScan:扫描 com.example.service 包,注册 @Service 类。
    • 解析 @Import(OtherConfig.class):递归处理 OtherConfig。
    • 解析 @Bean dataSource():生成工厂方法 BeanDefinition。
  3. 注册阶段 :
    • 注册 OtherConfig 及其 @Bean 方法。
    • 注册 dataSource 的 BeanDefinition。

5. 总结

Spring 对配置类注解的处理是一个分层递归的过程:

  1. 扫描 :通过 BeanDefinitionRegistry 筛选候选类。
  2. 解析 :ConfigurationClassParser 解析注解并生成中间结果(ConfigurationClass)。
  3. 注册 :ConfigurationClassBeanDefinitionReader 将解析结果转换为 BeanDefinition。

这种设计将注解元数据解析与 BeanDefinition 注册分离,确保了灵活性和扩展性(如支持动态 ImportSelector)。同时,递归处理和缓存机制解决了复杂依赖和性能问题。

相关推荐
小码哥_常1 小时前
一套自己部署的 IM,客户端是同一份 Flutter
前端·后端
学长毕设4 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
zzu123zsw6 小时前
从臃肿的模板调用到声明式契约:深入 UniHttp 动态代理与多渠道生命周期扩展
java·spring boot·microsoft·unihttp
源码集结号6 小时前
云诊所门诊系统源码,基于SpringBoot+Vue.js架构,支持多租户设计与Docker容器化部署
java·spring boot·源码·医院信息系统·门诊系统·诊所系统
努力的小雨7 小时前
给 AI 眼镜开发应用有多难?我从零做了一个陪练,还上架了
后端
论文复现现场7 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
啃瓜子的松鼠7 小时前
泛微OAe9-后端二开异步调用
java
秋天的怀念_David8 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
excel8 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
外收内放8 小时前
Python基础语法练习题(73)
java·前端·python