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 注解的 basePackagesbasePackageClasses

    2. 使用 ClassPathBeanDefinitionScanner 扫描类路径:

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

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

  • 流程

    1. 遍历配置类中的所有方法,筛选带 @Bean 注解的方法。
    2. 为每个 @Bean 方法生成一个 BeanDefinition
      • 类型ConfigurationClassBeanDefinition
      • 工厂方法 :设置为 @Bean 方法(通过 factoryMethodNamefactoryBeanName 指定)。
      • 依赖 :解析 @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 类等)。
  • BeanDefinitionattribute 字段存储配置类的元信息(如 @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 方法。
    • 注册 dataSourceBeanDefinition

5. 总结

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

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

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

相关推荐
张人大 Renda Zhang4 分钟前
2025 年版笔记:Java 开发如何用 AI 升级 CI/CD 和运维?
java·运维·ci/cd·ai·云原生·架构·自动化
Ankkaya7 分钟前
小白服务器踩坑(2)- 自动化部署
后端
回家路上绕了弯7 分钟前
Vavr 工具实用指南:Java 函数式编程的高效落地方案
分布式·后端
开心就好202510 分钟前
没有 Mac 怎么上架 iOS 应用 跨平台团队的可行交付方案分析
后端
阿里云云原生11 分钟前
AgentScope Java v1.0 发布,让 Java 开发者轻松构建企业级 Agentic 应用
java
aiopencode17 分钟前
构建可靠的 iOS 日志导出体系,从真机日志到系统行为的多工具协同实践
后端
Swizard25 分钟前
极限瘦身:将 Python AI 应用从 100MB 砍到 30MB
java·python·ai·移动开发
zhouyunjian36 分钟前
11、一文详解CompletableFuture:来源、定义、方法、与场景使用分析
java·网络·spring boot
Kin__Zhang37 分钟前
随手记录 UE4/CARLA 仿真器 segmentation fault
android·java·ue4
CoderYanger37 分钟前
A.每日一题——1523. 在区间范围内统计奇数数目
java·数据结构·算法·leetcode·职场和发展