Spring IoC容器与Bean生命周期详解

Spring IoC容器与Bean生命周期详解

定位:讲透 IoC 容器的两级接口、Bean 定义的注册来源、Bean 完整生命周期、核心组件分工与容器启动流程------这是理解整个 Spring 生态的地基

适用版本:Spring Framework 6.x(JDK 17+)


目录


一、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

关键认知:

  1. "拿到手的 Bean 可能不是原始对象":第 6 步后置处理器可能返回代理(AOP),容器存的是最终对象;
  2. 初始化三种方式的先后:@PostConstruct(JSR-250 注解,属第 4 步)→ InitializingBean 接口 → init-method;销毁反之 @PreDestroy → DisposableBean → destroy-method;
  3. 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

三个要点:

  1. ⑤ 和 ⑪ 是两个大动作:⑤把配置翻译成定义,⑪把定义变成活对象------绝大多数启动问题出在这两步;
  2. ⑨ onRefresh 是 Spring Boot 内嵌服务器的挂载点(BT 篇展开)------理解 Framework 的扩展点才能理解 Boot 的"魔法";
  3. 事件时机 :ContextRefreshedEvent 在全部单例就绪后发布,是"容器可用"的信号;关闭前对应 ContextClosedEvent。

六、总结

  1. IoC 容器:BeanFactory 是最小内核,ApplicationContext 叠加事件/国际化/资源/环境并即时初始化单例;容器读取元数据、管理 Bean 全生命周期。
  2. BeanDefinition 是枢纽:注解/Java/XML 全部翻译成它;先注册全部定义、后统一实例化,依赖不受声明顺序限制。
  3. 生命周期八阶段:实例化 → 属性注入 → Aware → BPP 前置(@PostConstruct)→ 初始化(InitializingBean → init-method)→ BPP 后置(AOP 代理)→ 使用 → 销毁(@PreDestroy → DisposableBean → destroy-method);容器里存的可能已是代理。
  4. 扩展点分工:BeanFactoryPostProcessor 改定义(实例化前),BeanPostProcessor 改对象(初始化前后);@Autowired 注入由 InstantiationAwareBeanPostProcessor 实现。
  5. 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)。

相关推荐
Wx-bishekaifayuan1 小时前
springboot房屋租赁系统11574-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·sql·spring·课程设计
OxYGC15 小时前
[AI工程] Spring AI 第廿一篇:存量 REST 接口的 MCP 化改造实录——工具从哪条路径注册、参数描述怎么写、身份怎么过去
java·人工智能·spring
卓怡学长16 小时前
w198基于springboot始于足下健康打卡平台的设计与实现
java·spring boot·spring·intellij-idea
自强的小白20 小时前
Spring事务失效的场景
java·spring
dadaobusi1 天前
Trace-driven 建模(基于轨迹/踪迹的建模)
java·后端·spring
南归北隐1 天前
Spring AI Alibaba Graph框架实现Tools工具调用
java·后端·spring·spring ai·spring ai tools
步行cgn1 天前
Spring 注解使用详解
java·spring
一条小小yu1 天前
Spring IoC的理解
java·后端·spring
yychen_java2 天前
第六篇:Spring AI 实战:将 Java 业务接口封装成企业级 MCP Server
java·人工智能·spring