Spring Bean 的生命周期是怎样的?
简化版
一个 Spring Bean 从生到死大致经历:实例化 → 属性注入 → Aware 回调 → BeanPostProcessor 前置 → 初始化(@PostConstruct / afterPropertiesSet / init-method)→ BeanPostProcessor 后置 → 使用 → 销毁。其中 AOP 代理通常在 BeanPostProcessor 后置处理阶段生成,这是很多「注入的是代理对象」问题的根源。
详细版
完整流程(单例 Bean):
- 实例化(Instantiation):容器根据
BeanDefinition用构造器创建对象(此时属性还是空的); - 属性注入(Populate):完成依赖注入(
@Autowired等); - Aware 回调:若实现了
BeanNameAware、BeanFactoryAware、ApplicationContextAware,容器回调,把容器信息塞给 Bean; - BeanPostProcessor 前置:
postProcessBeforeInitialization; - 初始化:依次执行
@PostConstruct→InitializingBean.afterPropertiesSet()→ 自定义init-method; - BeanPostProcessor 后置:
postProcessAfterInitialization,常见自动代理创建器会在这一阶段返回代理;但某些处理器也能在实例化前短路或为循环依赖提供早期代理,不能把所有代理创建都固定成唯一时点; - 使用:Bean 就绪,供业务调用;
- 销毁:容器关闭时,单例 Bean 执行
@PreDestroy→DisposableBean.destroy()→ 自定义 destroy-method。
⚠️ 一般业务 Bean 别过度实现
Aware接口——那会让你的代码和 Spring 强耦合,不利于测试和迁移。
完整版教学
一、抓住主线:创建 → 初始化 → 销毁
生命周期看着步骤多,其实就三大阶段:把对象造出来并填好依赖(实例化+注入)→ 做初始化收尾(各种 init 回调)→ 关闭时清理(各种 destroy 回调)。中间穿插的 Aware 和 BeanPostProcessor 是 Spring 留的扩展点。记住这条主线,细节顺序就好推了。
二、BeanPostProcessor:Spring 扩展能力的核心
这是这道题的题眼,也是面试高频追问点。BeanPostProcessor 是 Spring 的统一扩展入口,它在每个 Bean 初始化前后各插一脚:
postProcessBeforeInitialization:初始化前;postProcessAfterInitialization:初始化后。
Spring 大量核心功能都是通过它实现的。比如 @Autowired、@Value 的注入由 AutowiredAnnotationBeanPostProcessor 完成;@PostConstruct/@PreDestroy 由 CommonAnnotationBeanPostProcessor 处理。理解了 BeanPostProcessor,就理解了 Spring 一半的「魔法」——各种注解本质都是某个后置处理器在生命周期里做的事。
三、AOP 代理在哪一步生成——为什么注入的是代理
事务 @Transactional、缓存 @Cacheable、异步 @Async 等能力都会借助相应基础设施与自动代理创建器。正常完成初始化的常见路径中,代理会由 BeanPostProcessor 后置阶段返回;而实例化前短路、循环依赖的早期引用等特殊路径还能更早暴露代理,所以面试时宜说“通常在后置处理返回代理”,不要绝对化。
这带来一个重要结论:别的 Bean 注入进来的,是这个代理对象,不是原始对象。由此引出经典坑——同类内部方法调用事务失效:
@Service
class OrderService {
public void a() {
this.b(); // this 是目标对象内部调用,绕过代理,b() 的事务通知不生效
}
@Transactional
public void b() { ... }
}
a() 里用 this.b() 调用,this 指向原始对象而非代理,AOP 拦截不到,事务/缓存注解全失效。要生效得走代理(注入自己、或用 AopContext.currentProxy())。理解「代理在生命周期哪一步生成」,才能真正讲清这个坑。
四、初始化三兄弟的顺序和取舍
初始化回调有三种,执行顺序是 @PostConstruct → afterPropertiesSet() → init-method:
@PostConstruct:注解式,最常用、最简洁,推荐;InitializingBean.afterPropertiesSet():接口式,会和 Spring 耦合,不推荐业务用;init-method:XML/@Bean(initMethod=...)配置式,适合第三方类(改不了源码、加不了注解)。
五、单例 vs 原型的销毁差异
- 单例 Bean:容器管全程,关闭时会调销毁回调。
- 原型(prototype)Bean:容器只负责创建和初始化,创建后就「撒手不管」了——不会调用销毁回调,销毁责任在拿到它的调用方。所以原型 Bean 里放需要释放的资源(连接、文件句柄)要格外小心,Spring 不会帮你关。
六、用一条时间线核对回调顺序
假设 DataSourceClient 同时使用 3 种初始化回调和 3 种销毁回调,常规顺序不是任选其一,而是按容器约定依次执行。初始化阶段,@PostConstruct 由相应后处理器在初始化前回调期间触发,随后是 afterPropertiesSet(),最后是自定义 init method;销毁阶段则是 @PreDestroy、DisposableBean.destroy()、自定义 destroy method。
构造对象
→ 填充属性
→ Aware
→ BPP before
→ @PostConstruct
→ afterPropertiesSet
→ init-method
→ BPP after(常见路径可返回代理)
→ 对外可用
→ @PreDestroy → destroy → destroy-method
| 回调方式 | 与 Spring 耦合 | 常用场景 | 主要代价 |
|---|---|---|---|
@PostConstruct / @PreDestroy | 低,使用 Jakarta 注解 | 自有业务 Bean | 需要注解处理器 |
InitializingBean / DisposableBean | 高 | 框架集成 | 业务类依赖 Spring API |
initMethod / destroyMethod | 无需改目标类 | 第三方组件 | 配置与方法名要一致 |
初始化方法运行时 Bean 还处在创建流程中,不适合做长时间外部访问。若一次外部准备要 30 秒,把它直接塞进 @PostConstruct 会占住启动路径并增加初始化死锁风险,应考虑所有常规 singleton 就绪后的扩展点或应用就绪后的任务。
记忆钩子:先“造、填、感知”,再“前置、三种初始化、后置”,最后才对外暴露;销毁只对容器仍负责管理的对象生效。
七、常见误区与追问
- 误区:实例化和初始化是同一件事。 实例化只是得到对象,属性填充、初始化回调和后处理仍未完成;过早拿到引用可能看到不完整状态。
- 误区:所有 AOP 代理都只能在 postProcessAfterInitialization 创建。 这是常见正常路径,但实例化前短路和循环依赖早期引用存在特殊处理,表述不能绝对化。
- 误区:prototype Bean 关闭容器时也会自动执行销毁方法。 容器完成其创建和初始化后不再跟踪完整生命周期,资源清理由调用方负责。
- 追问:@Autowired 注入发生在哪一段? 相应的 InstantiationAwareBeanPostProcessor 在属性填充阶段参与依赖解析,早于常规初始化回调。
- 追问:为什么不建议业务类实现多个 Aware 接口? 它会把业务对象绑定到容器 API;只有确实需要 Bean 名、工厂或上下文能力时才应使用。
- 追问:BeanPostProcessor 和 BeanFactoryPostProcessor 有何区别? 前者处理 Bean 实例,后者在实例化普通 Bean 前修改 BeanDefinition 等容器元数据,两者作用对象和时机不同。
八、加强记忆
Bean 生命周期三大阶段:实例化+属性注入 → 初始化(Aware/BPP前置/@PostConstruct等/BPP后置)→ 销毁。核心是 BeanPostProcessor 这个扩展点,注解注入和自动代理都借助相应处理器;常规路径通常在后置处理中返回代理,特殊路径还可能提前暴露代理,而同类内部 this 调用始终不会重新经过代理。原型 Bean 的销毁 Spring 不管。