Bean生命周期与三级缓存

ℹ️本篇定位

"Bean 从生到死经历了什么"是 Spring 面试分水岭——只答"构造→注入→初始化→销毁"的人远多于能讲清三级缓存与循环依赖的人。本篇前置:IOCDI

一、Bean 生命周期全景

1. 实例化(Instantiation)
   └─ 构造器创建对象(反射 newInstance),属性都还是默认值

2. 属性填充(Populate)
   └─ 依赖注入:@Autowired / @Value / setter 注入,把依赖 Bean 塞进去

3. 初始化前(BeanPostProcessor.postProcessBeforeInitialization)
   └─ @PostConstruct 注解的方法在此执行

4. 初始化(InitializingBean.afterPropertiesSet / init-method)
   └─ 自定义初始化逻辑(连接池启动、资源预加载)

5. 初始化后(BeanPostProcessor.postProcessAfterInitialization)
   └─ **AOP 代理对象在这里生成**——这就是为什么从容器拿到的 Bean 可能是代理

6. 就绪(Ready)
   └─ Bean 可用,存在单例池(singletonObjects)中

7. 销毁(DisposableBean.destroy / @PreDestroy)
   └─ 容器关闭时调用,释放资源(连接池关闭、线程池 shutdown)

BeanPostProcessor 是 Spring 最关键的扩展点——它包裹了"初始化前后"两个钩子,所有框架级功能(@Autowired@Transactional@Async)都是通过自定义 BeanPostProcessor 实现的。

二、三级缓存与循环依赖

问题:A 依赖 B,B 依赖 A——两个 Bean 都等着对方先创建好,传统思路会死锁。

Spring 的答案——三级缓存

singletonObjects      一级:完全就绪的 Bean(经过实例化→填充→初始化→代理)
earlySingletonObjects 二级:早期引用(已经实例化,但还没填充属性)
singletonFactories    三级:ObjectFactory,能提前暴露"半成品"的引用

A 与 B 互相依赖的解决过程

  1. 创建 A → 实例化 A(反射调构造器)→ 把 A 的 ObjectFactory 放进三级缓存("谁要我给谁半成品")
  2. 填充 A 的属性 → 发现需要 B → 去创建 B
  3. 创建 B → 实例化 B → 把 B 的 ObjectFactory 放进三级缓存
  4. 填充 B 的属性 → 发现需要 A → 从三级缓存拿到 A 的半成品引用(此时 A 还没填充属性,但引用已经存在了)
  5. B 完成初始化 → 放进一级缓存
  6. 回到 A → 把 B 注入 → A 完成初始化 → 放进一级缓存

三个关键事实

  1. 三级缓存只解决单例 setter 注入的循环依赖——构造器注入的循环依赖无法解决(因为实例化这一步就卡住了,根本没机会暴露半成品);
  2. AOP 代理:三级缓存里 ObjectFactory 调用 getObject() 时,如果该 Bean 需要 AOP 代理,会返回代理对象而非原始对象——所以注入的依赖始终是"最终形态";
  3. 二级缓存的作用:防止每次获取都重新调用 ObjectFactory 生成代理(缓存最终的早期引用)。

三、Bean 作用域

作用域 含义 常见场景
singleton 整个容器一个实例(默认) 无状态 Bean(Service/DAO)
prototype 每次获取新实例 有状态 Bean(每次请求不同数据)
request 每次 HTTP 请求一个实例 Web 环境下请求级数据
session 每个 HTTP Session 一个实例 用户会话数据

prototype 的坑:Spring 不管理 prototype Bean 的完整生命周期——只负责创建,销毁由调用方自己管。因此 singleton Bean 里注入 prototype Bean 时,singleton 只会被注入一次(创建 singleton 时的那次),之后每次调用拿到的都是同一个——这不是"每次新实例",除非用 @LookupObjectProvider

四、高频面试题速答

  1. Bean 生命周期有哪些阶段? 见第一节全景图,关键点:BeanPostProcessor 的两个钩子、AOP 代理在初始化后生成。
  2. Spring 如何解决循环依赖? 三级缓存:实例化后暴露半成品引用(ObjectFactory),让依赖方先拿到引用再等填充完成。只支持单例 setter 注入的循环依赖。
  3. 构造器注入的循环依赖能解决吗? 不能——实例化这一步就卡住了,Bean 还没出生,三级缓存里没有它的半成品。
  4. prototype 作用域的循环依赖? 同样不能——prototype 不进入三级缓存,Spring 会直接抛 BeanCurrentlyInCreationException。
  5. 二级缓存为什么存在? 缓存 ObjectFactory 生成的早期引用(包括 AOP 代理),避免重复调用 getObject()。

勾连


⬅️ Spring AOP 🏠 00-Java ➡️ 05-Spring事务管理