Spring IoC容器与Bean生命周期详解
定位:讲透 IoC 容器的两级接口、Bean 定义的注册来源、Bean 完整生命周期、核心组件分工与容器启动流程------这是理解整个 Spring 生态的地基
适用版本:Spring Framework 6.x(JDK 17+)
目录
- [一、IoC 容器定位](#一、IoC 容器定位)
- [二、Bean 定义与注册](#二、Bean 定义与注册)
- [三、Bean 完整生命周期](#三、Bean 完整生命周期)
- 四、核心组件分工
- 五、容器启动流程(refresh)
- 六、总结
- 七、常见高频面试题
一、IoC 容器定位
1.1 IoC 与 DI
控制反转(Inversion of Control):对象的创建、组装、生命周期管理不再由使用者代码负责,而是交给容器------"控制权"从应用代码反转到框架。
依赖注入(Dependency Injection) 是 IoC 的具体实现手段:容器把依赖"注入"给需要它的对象。注入方式(构造器/字段/方法)与优劣在 02 篇展开,本篇只建立"容器管一切"的心智模型。
传统写法:UserService 自己 new UserDao(),耦合具体实现
IoC 写法:容器创建 UserDao 并注入给 UserService,使用者只面向接口
收益:解耦(面向接口)、可测试(注入替身)、集中管理(统一配置与生命周期)。
1.2 两级容器接口
BeanFactory ← 最基础:按定义创建/获取 Bean
▲
ApplicationContext ← 超集:企业级能力
| 能力 | BeanFactory | ApplicationContext |
|---|---|---|
| Bean 实例化与依赖注入 | ✓ | ✓ |
| 单例初始化时机 | 懒加载(getBean 时) | 启动即初始化非懒加载单例 |
| 事件发布(ApplicationEvent) | ✗ | ✓ |
| 国际化(MessageSource) | ✗ | ✓ |
| 资源访问(Resource) | ✗ | ✓ |
| 环境抽象(Environment) | 基础 | 完整 |
实践几乎只用 ApplicationContext(AnnotationConfigApplicationContext / Web 环境的 ServletWebServerApplicationContext 等);BeanFactory 的意义是理解"容器的最小内核"。
1.3 容器的四件事
读取元数据(注解/@Configuration/XML)→ 注册 BeanDefinition
按定义实例化、注入依赖、执行生命周期回调
按名称/类型提供 Bean、管理作用域
容器关闭时销毁回调
二、Bean 定义与注册
2.1 BeanDefinition 是什么
容器不直接保存对象,先保存Bean 的元数据描述 ------BeanDefinition:
| 元数据 | 对应配置 |
|---|---|
| beanClass | 实现类 |
| scope | @Scope(singleton/prototype...) |
| lazyInit | @Lazy |
| primary | @Primary |
| initMethod / destroyMethod | @Bean(initMethod=...)/destroyMethod |
| propertyValues / constructorArgs | 注入信息 |
| autowireMode | 自动装配模式 |
流程本质:一切配置(注解/Java/XML)先统一翻译成 BeanDefinition,后续流程只面对它------这是"配置方式多样、内核只有一套"的原因。
2.2 注册来源与处理时机
| 来源 | 方式 |
|---|---|
| 组件扫描 | @Component 及派生(@Service/@Repository/@Controller/@Configuration),由 @ComponentScan 触发 |
| Java 配置 | @Configuration 类中的 @Bean 方法 |
| XML | <bean>(遗留系统) |
| API | BeanDefinitionRegistry#registerBeanDefinition(动态注册,框架扩展常用) |
处理时机在容器刷新(refresh)阶段:先扫描解析并注册全部定义,再统一实例化------因此 Bean 之间的依赖不受声明顺序限制(构造器循环依赖除外,见 02 篇)。
三、Bean 完整生命周期
单例 Bean 的完整阶段链(面试最高频的一张图):
1. 实例化(Instantiation)
构造器创建原始对象(尚未注入任何依赖)
│
2. 属性填充(Populate)
依赖注入:@Autowired/@Resource、@Value、setter
│
3. Aware 回调
BeanNameAware → BeanFactoryAware → ApplicationContextAware
│
4. BeanPostProcessor#postProcessBeforeInitialization
(@PostConstruct 在此阶段由 CommonAnnotation 处理器执行)
│
5. 初始化(顺序固定)
InitializingBean#afterPropertiesSet → 自定义 init-method
│
6. BeanPostProcessor#postProcessAfterInitialization
(AOP 代理对象通常在此产生,返回的可能是代理)
│
7. 就绪,放入容器供使用
│
8. 容器关闭时销毁(顺序固定)
@PreDestroy → DisposableBean#destroy → 自定义 destroy-method
关键认知:
- "拿到手的 Bean 可能不是原始对象":第 6 步后置处理器可能返回代理(AOP),容器存的是最终对象;
- 初始化三种方式的先后:@PostConstruct(JSR-250 注解,属第 4 步)→ InitializingBean 接口 → init-method;销毁反之 @PreDestroy → DisposableBean → destroy-method;
- prototype Bean 不走销毁回调:容器注入后即放手,销毁责任在使用方。
四、核心组件分工
生命周期各阶段由不同扩展点负责,理解分工才能正确扩展:
| 组件 | 作用层级 | 时机 | 典型用途 |
|---|---|---|---|
BeanDefinitionRegistry |
定义 | 注册期 | 存取 BeanDefinition |
BeanFactoryPostProcessor |
容器级 | 实例化之前 | 修改/补充 Bean 定义(@Value 占位符解析器即此类) |
InstantiationAwareBeanPostProcessor |
Bean 级 | 实例化前后、属性注入时 | 提前返回代理、注入介入(@Autowired 注入由其实现) |
BeanPostProcessor |
Bean 级 | 初始化前后 | 包装对象:AOP 代理、@PostConstruct 处理 |
两条辨析:
- BeanFactoryPostProcessor vs BeanPostProcessor:前者改"定义"(对象还没创建),后者改"对象"(实例已存在);
- Aware 接口:让 Bean 感知容器基础设施(自己的名字、所在工厂、上下文),是框架级集成入口,业务代码通常不需要。
五、容器启动流程(refresh)
AbstractApplicationContext#refresh() 是容器启动的骨架(模板方法),主干步骤:
① prepareRefresh 准备:时间戳、环境校验、占位符属性
② obtainFreshBeanFactory 创建/刷新 BeanFactory,加载 BeanDefinition
③ prepareBeanFactory 配置工厂:类加载器、Aware 处理器、默认注册
④ postProcessBeanFactory 子类扩展(Web 环境注册作用域等)
⑤ invokeBeanFactoryPostProcessors 执行所有 BeanFactoryPostProcessor
(配置类解析、组件扫描在此完成)
⑥ registerBeanPostProcessors 注册所有 BeanPostProcessor
⑦ initMessageSource 国际化
⑧ initApplicationEventMulticaster 事件广播器
⑨ onRefresh 子类扩展(Boot 在此创建内嵌 Web 服务器)
⑩ registerListeners 注册监听器
⑪ finishBeanFactoryInitialization 实例化所有非懒加载单例 ★
⑫ finishRefresh 清缓存、发布 ContextRefreshedEvent
三个要点:
- ⑤ 和 ⑪ 是两个大动作:⑤把配置翻译成定义,⑪把定义变成活对象------绝大多数启动问题出在这两步;
- ⑨ onRefresh 是 Spring Boot 内嵌服务器的挂载点(BT 篇展开)------理解 Framework 的扩展点才能理解 Boot 的"魔法";
- 事件时机 :
ContextRefreshedEvent在全部单例就绪后发布,是"容器可用"的信号;关闭前对应ContextClosedEvent。
六、总结
- IoC 容器:BeanFactory 是最小内核,ApplicationContext 叠加事件/国际化/资源/环境并即时初始化单例;容器读取元数据、管理 Bean 全生命周期。
- BeanDefinition 是枢纽:注解/Java/XML 全部翻译成它;先注册全部定义、后统一实例化,依赖不受声明顺序限制。
- 生命周期八阶段:实例化 → 属性注入 → Aware → BPP 前置(@PostConstruct)→ 初始化(InitializingBean → init-method)→ BPP 后置(AOP 代理)→ 使用 → 销毁(@PreDestroy → DisposableBean → destroy-method);容器里存的可能已是代理。
- 扩展点分工:BeanFactoryPostProcessor 改定义(实例化前),BeanPostProcessor 改对象(初始化前后);@Autowired 注入由 InstantiationAwareBeanPostProcessor 实现。
- refresh 十二步:⑤ 解析配置、⑪ 实例化单例是两大关键,⑨ 是 Boot 内嵌服务器挂载点,⑫ 发布 ContextRefreshedEvent 标志就绪。
七、常见高频面试题
1. BeanFactory 和 ApplicationContext 的区别?
要点:BeanFactory 是最基础容器,提供 Bean 的实例化与依赖注入,单例懒加载(getBean 时才创建);ApplicationContext 是其超集,增加事件发布、国际化、资源访问、环境抽象,并在启动时即初始化所有非懒加载单例(提前暴露配置错误)。实践基本都用 ApplicationContext;BeanFactory 的意义在于理解容器最小内核。
2. 完整描述一个 Bean 的生命周期。
要点:① 实例化(构造器创建原始对象);② 属性填充(依赖注入);③ Aware 回调(BeanName/BeanFactory/ApplicationContext);④ BeanPostProcessor 前置处理(@PostConstruct 在此);⑤ 初始化(InitializingBean#afterPropertiesSet → 自定义 init-method);⑥ BeanPostProcessor 后置处理(AOP 代理在此产生);⑦ 就绪使用;⑧ 容器关闭时销毁(@PreDestroy → DisposableBean → destroy-method)。注意拿到的可能是代理对象,且 prototype 不执行销毁回调。
3. BeanPostProcessor 和 BeanFactoryPostProcessor 的区别?
要点:作用对象与时机不同。BeanFactoryPostProcessor 作用于容器级,在所有 Bean 实例化之前执行,修改或补充的是 BeanDefinition(元数据),典型如属性占位符解析;BeanPostProcessor 作用于单个 Bean,在实例已创建后的初始化前后执行,可包装/替换对象(AOP 代理、@PostConstruct 处理)。简记:前者改"图纸",后者改"成品"。
4. 什么是 BeanDefinition?注解、Java 配置、XML 三种方式最终如何统一?
要点:BeanDefinition 是 Bean 的元数据描述:类名、作用域、懒加载、@Primary、初始化/销毁方法、依赖与属性等。三种配置方式(@Component 扫描、@Configuration+@Bean、XML )在容器刷新阶段都被解析翻译成 BeanDefinition 注册进容器,后续实例化流程只面对它。这是"配置入口多样、内核统一"的设计,也解释了为什么配置方式可以混用。
5. @PostConstruct、InitializingBean、init-method 的执行顺序?
要点:@PostConstruct 最先(由注解处理器在 BeanPostProcessor 前置阶段执行),随后是 InitializingBean#afterPropertiesSet,最后才是自定义 init-method;销毁顺序镜像:@PreDestroy → DisposableBean#destroy → destroy-method。实际项目推荐注解方式(不侵入接口、声明清晰);init-method 用于不想让业务类依赖 Spring 接口的场景。
6. refresh() 里最关键的步骤是哪些?
要点:两个大动作------invokeBeanFactoryPostProcessors(执行所有容器级后置处理器,配置类解析与组件扫描在此完成,BeanDefinition 就绪)和 finishBeanFactoryInitialization(实例化所有非懒加载单例,生命周期全链路在此跑完)。另外 onRefresh 是 Spring Boot 创建内嵌 Web 服务器的挂载点,finishRefresh 发布 ContextRefreshedEvent 标志容器就绪。启动问题排查基本围绕这两步。
7. 容器中的单例 Bean 和单例模式是一回事吗?
要点:不是。设计模式的单例指整个 JVM 中某类只有一个实例,由类自己控制;Spring 单例是"每个容器中该 Bean 定义只有一个实例",由容器的单例注册表(singletonObjects)管理------同一类可以注册多个不同名的 Bean 各自单例,不同容器也可各有一份。且 Spring 单例默认无状态才安全,有状态单例存在并发问题。
8. 为什么容器里 getBean 拿到的对象可能不是原始类的实例?
要点:生命周期第 6 步(BeanPostProcessor 后置处理)允许返回替换对象------AOP 在此把原始 Bean 包装成代理(JDK 动态代理或 CGLIB 子类),容器存储与注入的都是代理。后果:对 Bean 做 instanceof 具体类判断在 JDK 代理下会失败;代理不继承具体类,只实现接口。这也是"循环依赖注入的可能是半成品/代理"等复杂问题的根源(02 篇展开)。
9. @Component 和 @Bean 有什么区别?
要点:作用层级不同。@Component(及 @Service/@Repository/@Controller)标注在类上,由组件扫描发现并注册,适合自有业务类;@Bean 标注在 @Configuration 类的方法上,方法返回值被注册为 Bean,适合把第三方类(无源码无法加注解)或需要条件/多版本创建的类纳入容器。两者最终都生成 BeanDefinition。@Configuration 类本身也是一个 Bean,且被 CGLIB 增强保证 @Bean 方法间调用返回同一实例。
10. prototype 作用域的 Bean 生命周期有什么不同?
要点:容器每次 getBean(或注入)都创建新实例,不缓存;关键差异是容器不管理其销毁------不执行销毁回调(@PreDestroy/DisposableBean),释放资源的责任在获取它的一方。因此有状态对象(如持有资源句柄)用 prototype 时要自行管理清理;单例 Bean 依赖 prototype 时,每次拿到的都是新实例,但单例自身只创建一次(注入不会自动刷新,需要 @Lookup 或 ObjectProvider)。
