Spring Boot 注解方式如何扫描并注册 BeanDefinition?

Spring Boot 注解方式如何扫描并注册 BeanDefinition?


1. 引言

在 Spring Boot 时代,「零 XML」几乎成了标配------只写几个注解,容器就能自动把 Bean 注册进来。

可你有没有好奇过:Spring 到底是怎么把散落在各个包里的类,变成内存里的 BeanDefinition 的?


2. 全景概览(一张图先记住)

less 复制代码
@SpringBootApplication
└─ @ComponentScan   ------ 1. 指定扫描范围
   ↓
ConfigurationClassPostProcessor  ------ 2. 解析所有配置类
   ↓
ConfigurationClassParser#doProcessConfigurationClass
   ↓
ComponentScanAnnotationParser → ClassPathBeanDefinitionScanner
   ↓
ASM 读取 *.class 字节码  ------ 3. 无需加载类即可拿到注解元数据
   ↓
过滤 → 生成 ScannedGenericBeanDefinition  ------ 4. 封装成 BeanDefinition
   ↓
DefaultListableBeanFactory.registerBeanDefinition()  ------ 5. 塞进 ConcurrentHashMap

下面分步展开。


3. 启动入口:@SpringBootApplication

java 复制代码
@SpringBootApplication   // 复合注解
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@SpringBootApplication =
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan // ← 今天的主角

默认扫描 当前主类所在包及其子包 ,可通过 basePackages / basePackageClasses 调整。


4. 触发时机:ConfigurationClassPostProcessor

  1. 容器启动 refresh()invokeBeanFactoryPostProcessors()
  2. 优先级最高的 BeanDefinitionRegistryPostProcessor 就是
    ConfigurationClassPostProcessor(简称 CCPP)。
  3. CCPP 会解析所有标了 @Configuration 的类(包括主类),找出 @ComponentScan 并真正执行扫描。

org.springframework.context.support.AbstractApplicationContext#refresh

java 复制代码
try {
    // Allows post-processing of the bean factory in context subclasses.
    postProcessBeanFactory(beanFactory);

    StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
    // Invoke factory processors registered as beans in the context.
    invokeBeanFactoryPostProcessors(beanFactory);
    // Register bean processors that intercept bean creation.
    registerBeanPostProcessors(beanFactory);
    beanPostProcess.end();

    // Initialize message source for this context.
    initMessageSource();

    // Initialize event multicaster for this context.
    initApplicationEventMulticaster();

    // Initialize other special beans in specific context subclasses.
    onRefresh();

    // Check for listener beans and register them.
    registerListeners();

    // Instantiate all remaining (non-lazy-init) singletons.
    finishBeanFactoryInitialization(beanFactory);

    // Last step: publish corresponding event.
    finishRefresh();
}

5. 核心源码链路

阶段 关键类 / 方法 作用
解析配置类 ConfigurationClassParser#doProcessConfigurationClass 发现 @ComponentScan
转给扫描器 ComponentScanAnnotationParser#parse 封装扫描参数
执行扫描 ClassPathBeanDefinitionScanner#doScan 遍历包路径
类元数据 ASM MetadataReader 不加载类就能读注解
条件过滤 ConditionEvaluator 处理 @ConditionalOnClass
注册定义 DefaultListableBeanFactory#registerBeanDefinition 放入 ConcurrentHashMap

6. 细节拆解

6.1 包路径如何变成 *.class 文件?

ResourcePatternResolvercom.example.demo 解析成

ruby 复制代码
classpath*:com/example/demo/**/*.class

支持 jar、file、nested jar 等多种协议。

6.2 为什么不用反射?------ ASM 提速

Spring 使用 org.springframework.asm.ClassReader 直接解析字节码,拿到:

  • 类名、修饰符
  • 注解信息(@Component@Scope@Lazy ...)

好处

  • 不触发类加载,快
  • 避免 ClassNotFoundException 陷阱

6.3 过滤规则一览

注解 默认是否包含 说明
@Component ✔️ 元注解,派生 @Service/@Repository/@Controller
@Conditional... ✔️ 不满足条件直接跳过
includeFilters / excludeFilters 自定义 可扩展自己的 TypeFilter

6.4 BeanDefinition 长什么样?

java 复制代码
ScannedGenericBeanDefinition bd = new ScannedGenericBeanDefinition(metadata);
bd.setBeanClassName("com.example.demo.service.UserService");
bd.setScope("singleton");
bd.setLazyInit(false);
bd.setPrimary(false);
...
registry.registerBeanDefinition("userService", bd);

7. @Import & @EnableAutoConfiguration 补充

  • @Import 引入的普通类会递归解析;
    如果是 ImportSelector,会再批量导入一批配置类。
  • @EnableAutoConfiguration 通过 spring.factories / spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 加载 AutoConfiguration 类 ,同样走条件过滤 → 注册 BeanDefinition

自动配置类也是靠同一套 ConfigurationClassParser 机制,只是入口不同。


8. 调试技巧速查表

目标 操作
想看扫到哪些类 logging.level.org.springframework.context.annotation=DEBUG
想看条件评估 logging.level.org.springframework.boot.autoconfigure=DEBUG
断点调试 ConfigurationClassParser#doProcessConfigurationClass
加速冷启动 引入 spring-context-indexer 并编译时生成 META-INF/spring.components

9. 小结一句话

Spring Boot 的"魔法"并不神秘:启动时用一个后置处理器(CCPP)解析 @ComponentScan,借助 ASM 把类变成 BeanDefinition 塞进容器,后面 getBean 时直接实例化即可。

搞懂这一条链路,阅读 Spring Boot 源码或写自定义 starter 都会事半功倍。


相关推荐
CodeSheep1 小时前
这个老牌知名编程论坛,彻底倒下了!
前端·后端·程序员
*才华有限公司*1 小时前
#从401到200:Spring Boot + Vue 静态资源访问全链路问题解决方案
vue.js·spring boot·后端
superman超哥1 小时前
Rust 异步并发基石:异步锁(Mutex、RwLock)的设计与深度实践
开发语言·后端·rust·编程语言·rust异步并发·rust异步锁·rust mutex
叫我:松哥2 小时前
基于Flask开发的智能招聘平台,集成了AI匹配引擎、数据预测分析和可视化展示功能
人工智能·后端·python·信息可视化·自然语言处理·flask·推荐算法
IT_陈寒2 小时前
Java开发者必知的5个性能优化技巧,让应用速度提升300%!
前端·人工智能·后端
牧小七2 小时前
springboot配置maven激活配置文件
spring boot·后端·maven
nbsaas-boot2 小时前
Go 语言中的集合体系:从语言设计到工程实践
开发语言·后端·golang
李日灐2 小时前
C++STL:deque、priority_queue详解!!:详解原理和底层
开发语言·数据结构·c++·后端·stl
麦兜*2 小时前
Spring Boot整合Swagger 3.0:自动生成API文档并在线调试
java·spring boot·后端
接着奏乐接着舞。2 小时前
Go 一小时上手指南:从零到运行第一个程序
开发语言·后端·golang