spring三级缓存解决循环依赖问题

0 阅读9分钟

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 FactoriesObject 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>完整就绪的单例 BeanBean 创建全部完成后容器销毁时存放最终成品,获取 Bean 优先查这里
earlySingletonObjects(二级)Map<String, Object>实例化完成、未填充属性的早期对象(原始 / 代理)三级工厂执行后Bean 放入一级缓存时缓存工厂执行结果,避免重复代理
singletonFactories(三级)Map<String, ObjectFactory<?>>ObjectFactory 工厂回调Bean 实例化完成后工厂执行后 / Bean 放入一级缓存时延迟生成 AOP 代理,只有被引用才执行