Bean 的生命周期:从出生到销毁
Spring 容器启动时,Bean 不是"new 完就完事",它要走一整套流程:实例化 → 属性注入 → 初始化 → 就绪使用 → 销毁。搞懂这条流水线,你才能解释"为什么 @PostConstruct 里拿不到代理对象""为什么 @Lazy 能解决启动慢"这类玄学问题。
论Bean 一生的五个阶段
① 实例化(Instantiation):Spring 调用构造方法,把对象在内存里 new 出来。此时属性还是 null。
② 属性注入(Populate):把 @Autowired 标注的依赖、@Value 标注的值,一个个塞进对象的字段。
③ 初始化(Initialization):回调 @PostConstruct 方法、执行 InitializingBean.afterPropertiesSet()、调用 init-method。这时 Bean 已经能用了。
④ 使用(In Use):你从容器里 getBean 出来,正常调用。整个应用运行期间都在这一步。
⑤ 销毁(Destruction):容器关闭时回调 @PreDestroy、DisposableBean.destroy()、destroy-method。清理资源。
用 @PostConstruct 和 @PreDestroy 感受一下生命周期
三种初始化/销毁回调,用哪个
| 回调方式 | 写法 | 评价 |
|---|---|---|
| @PostConstruct / @PreDestroy | 注解标在方法上,Jakarta 标准。 | 推荐 最简单,零侵入。 |
| InitializingBean / DisposableBean | 实现接口,重写 afterPropertiesSet() / destroy()。 | 了解 与 Spring 耦合,不推荐。 |
| init-method / destroy-method | XML 或 @Bean 上指定方法名。 | 老项目 XML 时代的做法。 |
新项目一律用 @PostConstruct 和 @PreDestroy,别再去实现那些接口。
Bean 作用域:单例、原型、请求、会话
Spring 默认 Bean 是 singleton(单例)——整个容器里只有一个实例,所有人共享。但有些对象每次用都得新造一个,那就用 prototype(原型)。Web 环境下还有 request、session 这种跟请求周期绑定的作用域。
| 作用域 | 含义 | 典型场景 |
|---|---|---|
| singleton | 整个容器一个实例(默认)。 | Service、Dao、配置类——无状态对象。 |
| prototype | 每次 getBean 都 new 一个新的。 | 有状态对象,比如表单 Bean、任务实例。 |
| request | 每个 HTTP 请求一个实例。 | Web 层,请求级上下文。 |
| session | 每个用户会话一个实例。 | 购物车、登录态。 |
切换作用域和懒加载
BeanPostProcessor:Spring 自己的"切面"
Spring 内部大量使用 BeanPostProcessor——在 Bean 初始化前后插手,对 Bean 做加工。AOP 代理、@Autowired 注入、@PostConstruct 回调,底层都是 BeanPostProcessor 实现的。你自己写框架时也会用到它。
一个打日志的 BeanPostProcessor
单例 Bean 里注入原型 Bean,原型只会被注入一次!因为单例在容器启动时就创建好了,它持有的那个 prototype 引用永远是第一次注入的那个。想每次都拿到新的,要么让 singleton 实现 ApplicationContextAware 每次 getBean,要么用 @Lookup 注解。新手踩这个坑能怀疑人生。
1.(流程题)请按顺序说一个 Spring Bean 从创建到销毁走了哪些步骤?
查看答案
答案:实例化(调构造器)→ 属性填充(@Autowired 注入)→ Aware 接口回调(BeanNameAware 等)→ BeanPostProcessor 前置处理 → 初始化(@PostConstruct、afterPropertiesSet、init-method)→ BeanPostProcessor 后置处理(AOP 代理就在这一步生成)→ 就绪使用 → 销毁(@PreDestroy、destroy-method)。解析:能说出"后置处理器在初始化前后各调一次、AOP 代理在这一步织入",面试官就知道你懂底层。
2.(高频题)什么是循环依赖?Spring 的三级缓存分别存了什么?为什么构造器循环依赖解决不了?
查看答案
答案:A 依赖 B、B 又依赖 A 就是循环依赖。三级缓存:一级 singletonObjects(成品单例)、二级 earlySingletonObjects(提前暴露的半成品)、三级 singletonFactories(对象工厂,用来生成早期引用,AOP 场景靠它决定要不要提前织入代理)。解析:三级缓存只解决单例的 setter/字段注入循环依赖;构造器循环依赖是"对象还没 new 完就要求 new 别人",逻辑上死锁,无解,只能重新设计。
3.(思考题)为什么 Spring 要用三级缓存,两级不够吗?
查看答案
答案:关键在于 AOP。如果 B 要注入的是 A 的代理对象,三级缓存的工厂能保证"提前暴露出去的就是最终的代理对象",不会出现"先暴露原始对象、后来又换成代理"的不一致。两级缓存做不到这一点。解析:一句话:三级缓存是为了在有 AOP 代理时,也能优雅地暴露早期引用。