spring三级缓存(解决循环依赖)
什么是循环依赖
多个 Bean 互相依赖对方,形成闭环。
//A依赖B
@Component
public class A{
@Autowired
private B b;
}
//B依赖A
@Component
public class B{
@Autowired
private A a;
}
A 需要 B 才能完成创建,B 又需要 A 才能完成创建 → 循环依赖
分类
字段/setter注入(单例): spring可以通过三级缓存解决
构造器注入: Spring 无法解决,直接抛出BeanCurrentlyInCreationException
prototype 作用域 Bean:完全不支持循环依赖
为什么构造器注入的循环依赖不能解决
构造器注入:调用构造方法的时候,就必须把依赖全部传入。
@Component
public class A{
public A(B b){}
}
创建 A 的时候,调用构造函数,就必须拿到 B;创建 B 又要 A。
对象还没有实例化出来,没有机会把半成品对象放入三级缓存,直接卡死。
setter / 字段注入流程:先 new 对象(实例化)→ 对象已经存在,放入三级缓存 → 再填充属性。
对象已经实例完成,才有机会暴露半成品。
三级缓存
仅针对 单例、setter / 字段注入;构造器注入无法解决循环依赖。
三级缓存都存于 DefaultSingletonBeanRegistry 类中。
// 一级缓存:完整实例化完成的单例Bean
private final Map<String, Object> singletonObjects;
// 二级缓存:实例化完成,但还未填充属性、未初始化的原始Bean对象(early‑reference)
private final Map<String, Object> earlySingletonObjects;
// 三级缓存:Bean工厂对象,用于生成Bean的早期代理对象
private final Map<String, ObjectFactory<?>> singletonFactories;
各类缓存作用
一级缓存 singletonObjects
存放:完全创建完毕(实例化 + 属性填充 + 初始化)的单例 Bean。
作用:Bean 最终成品仓库。
获取逻辑:获取 Bean 优先查一级缓存。如果在这里拿到对象,直接返回,流程结束。
循环依赖过程中,Bean 还没完成属性填充,不会放入一级缓存。
三级缓存 singletonFactories
存放:Object Factory(lambda / 工厂回调),不是 Bean 对象本身。
对象实例化完成(new 出来对象),属性还没填充,就把一个 lambda 工厂放入三级缓存。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
核心作用:用于在需要的时候生成该 Bean 的 AOP 代理对象。
如果这个 Bean 需要 AOP 代理,工厂会返回代理对象;不需要代理,直接返回原始对象。
注意:不会立刻执行工厂方法,只有别的 Bean 需要引用它的时候,才执行工厂拿到对象。
为什么要用工厂而不是直接放对象?
不是所有 Bean 都发生循环依赖;大部分 Bean 不需要提前代理。延迟执行代理逻辑,节省性能。
二级缓存 earlySingletonObjects
存放:已经调用三级工厂生成出来的早期对象(原始对象 / 代理对象),实例化完成,但属性未填充
作用:缓存三级工厂执行后的结果,避免重复执行代理逻辑。
当从三级缓存执行 Object Factory 拿到对象之后,就把对象放到二级缓存,同时删除三级缓存中的 factory。
后续其他 Bean 再依赖这个 Bean,直接从二级缓存取,不再重复执行代理。
二级缓存解决:同一个 Bean 被多个 Bean 循环引用时,不要重复创建代理。
完整三级缓存处理循环依赖流程(A 依赖 B,B 依赖 A)
1.创建 A:实例化 new A 对象;属性还没填充。把 A 的代理工厂放入三级缓存 singletonFactories。
**2.**填充 A 的属性,发现需要 B;开始创建 B。
3.创建 B:实例化 new B 对象;把 B 的代理工厂放入三级缓存。
**4.**填充 B 的属性,发现需要 A。尝试获取 A。
1.一级缓存无 A;
2.查询三级缓存,执行 A 的 ObjectFactory,得到 A 的早期对象(原始 / 代理);
3.将 A 放入二级缓存 earlySingletonObjects,删除三级缓存中的 A 工厂。
5. B 拿到 A 早期对象,完成 B 属性填充、初始化;B 完成,放入一级缓存 singletonObjects。
6. ** A 拿到完整的 B 对象,完成 A 属性填充、初始化;A 完成,放入一级缓存**,删除二、三级缓存中 A 的记录。
| 缓存 | 存储内容 | 核心作用 |
|---|---|---|
| 一级 singleton Objects | 完整就绪的单例 Bean | 存放最终成品 Bean;优先获取完整 Bean |
| 二级 earlySingleton Objects | 实例化完成、未填充属性的 Bean (原始 / 代理) | 保存三级工厂执行后的对象,避免重复代理 |
| 三级 singleton Factories | Object Factory 对象工厂 (lambda) | 延迟生成 Bean 的早期代理对象,只有被别人依赖才执行 |
关键面试点
1.如果没有 AOP 代理,理论上二级缓存就可以解决循环依赖;引入三级缓存是为了延迟 AOP 代理创建。
2.构造器注入无法解决:对象还没有实例化,根本没有机会放入三级缓存。
3.仅支持 scope=singleton;prototype 多实例不支持循环依赖.
4.Bean 创建完成后,二、三级缓存会清理,只有一级缓存保留对象。
Spring 必须靠三级缓存才能解决循环依赖吗
结论:不是。
如果项目完全没有 AOP 代理,只用【一级缓存 + 二级缓存】就可以解决循环依赖。
Spring 设置三级缓存(singletonFactories)是专门为处理 AOP 代理场景,不是解决普通对象循环依赖的必需品。
场景1: Bean 不需要 AOP 代理(无 @Transactional、无切面)
此时三级缓存里面的工厂执行,直接返回原始对象。
逻辑等价:实例化完直接把原始对象放入二级缓存,不需要工厂。
这种场景,两级缓存就足够解决循环依赖。
场景2: Bean 需要 AOP 代理(如加了 @Transactional)
这是三级缓存存在的真正理由。
Bean 刚实例化完成,属性还没填充,正常 AOP 代理是在初始化之后才创建。
但是发生循环依赖时,别的 Bean 要提前引用这个 Bean,此时必须提前生成代理对象。
三级缓存存放的是回调工厂:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
getEarlyBeanReference():如果需要代理,就提前生成代理;不需要代理返回原对象。
关键点:工厂不会立刻执行。只有其他 Bean 需要引用这个 Bean(触发循环依赖),才执行工厂,生成代理对象。
如果没有发生循环依赖,工厂永远不执行,代理还是走正常流程(初始化之后创建),避免无谓提前代理,提升性能。
如果去掉三级缓存,只保留一级 + 二级会发生什么?
方案:实例化 Bean 之后,直接生成代理对象放入二级缓存。
功能上可以解决循环依赖,但是性能有代价:
所有单例 Bean,实例化完成之后,不管会不会发生循环依赖,全部提前创建代理对象。
大部分 Bean 根本不会出现循环依赖,白白提前做 AOP 代理,浪费 CPU。
Spring 设计者选择:不提前生成代理,把代理逻辑封装进 ObjectFactory,延迟触发,于是引入三级缓存。
模拟流程:只有一级 + 二级缓存、无 AOP(可以正常解决循环依赖 A↔B)
1. new A,实例化,放入二级缓存
2. A 填充属性发现需要 B → new B,放入二级缓存
3. B 填充属性发现需要 A,从二级缓存拿到原始 A 对象
4. B 填充属性、初始化完成,移入一级缓存
5. A 拿到 B,填充属性初始化完成,移入一级缓存
整个流程正常跑完,循环依赖解决。
注意: 一旦有 AOP 代理,两级缓存方案就有问题:要么提前全部代理损耗性能,要么拿不到代理对象。
高频面试问答
Q:三级缓存是用来解决循环依赖的吗?
A:不完全准确。
真正用来 “拿到半成品对象打破闭环” 的是一、二级缓存;
三级缓存的核心目的是延迟 AOP 代理对象的创建,兼顾性能和循环依赖场景。
Q:那为什么网上都说三级缓存解决循环依赖?
A:因为 Spring 容器要同时兼容两种情况:普通 Bean + AOP 代理 Bean。 在 Spring 完整源码实现中,为了兼容 AOP,最终使用了三级缓存这套整体方案。所以整体上说 Spring 靠三级缓存机制解决循环依赖;但不能理解为 “没有三级缓存就一定不能解决循环依赖”。
Q:能不能自己写代码实现循环依赖,只使用两级缓存?
A:可以,前提是你的业务不做 AOP 动态代理。一旦引入代理逻辑,两级缓存就需要提前创建代理,带来性能损耗。
循环依赖方案对比
方案对比:两级缓存 vs 三级缓存
| 对比维度 | 两级缓存方案(一级 + 二级,无 AOP) | Spring 原生三级缓存方案 |
|---|---|---|
| 缓存结构 | singletonObjects(完整 Bean) + earlySingletonObjects(早期对象) | singletonObjects + earlySingletonObjects + singletonFactories(工厂回调) |
| 三级缓存作用 | 不存在 | 存放ObjectFactory lambda 工厂,延迟生成 AOP 代理对象 |
| 能否解决普通 Bean 循环依赖 | ✅ 可以 | ✅ 可以 |
| 能否解决带 AOP 代理的循环依赖 | ❌ 不行(要么拿不到代理,要么全部提前代理) | ✅ 可以,工厂回调提前生成代理 |
| AOP 代理创建时机 | 实例化后立即创建(所有 Bean 都提前代理) | 发生循环依赖被引用时才执行工厂创建代理;无循环依赖则正常初始化后代理 |
| 性能开销 | 高,所有单例 Bean 实例化后都提前做代理 | 低,只有真正发生循环依赖的 Bean 才提前代理 |
| 代理对象是否唯一 | 可能重复生成多份代理(无工厂缓存) | 二级缓存缓存工厂执行结果,保证单例 |
| 适用场景 | 极简容器、无 AOP、无切面 | 生产级完整 Spring 容器,兼容 AOP / 事务 / 切面 |
| 构造器注入能否解决 | ❌ 不能 | ❌ 不能(与缓存级别无关) |
| prototype 作用域能否解决 | ❌ 不能 | ❌ 不能(不进入缓存) |
三级缓存各层职责速查表
| 缓存名称 | 类型 | 存什么 | 什么时候放入 | 什么时候移除 | 核心作用 |
|---|---|---|---|---|---|
singletonObjects(一级) | Map<String, Object> | 完整就绪的单例 Bean | Bean 创建全部完成后 | 容器销毁时 | 存放最终成品,获取 Bean 优先查这里 |
earlySingletonObjects(二级) | Map<String, Object> | 实例化完成、未填充属性的早期对象(原始 / 代理) | 三级工厂执行后 | Bean 放入一级缓存时 | 缓存工厂执行结果,避免重复代理 |
singletonFactories(三级) | Map<String, ObjectFactory<?>> | ObjectFactory 工厂回调 | Bean 实例化完成后 | 工厂执行后 / Bean 放入一级缓存时 | 延迟生成 AOP 代理,只有被引用才执行 |