Spring循环依赖

xiaopeng
发布于 2026-08-13 / 3 阅读
0
0

Spring循环依赖

Spring 循环依赖与三级缓存 ⭐⭐⭐⭐⭐

面试频率:★★★★★
工作频率:★★★☆☆


⚡30 秒速记(复习只看这里)

🟢 一句话

Spring 通过三级缓存 + 提前暴露 Bean 引用,解决部分单例 Bean 的循环依赖问题。

例如:

A依赖B

B又依赖A

核心思想:

A还没完全创建完成

↓

先提供获取A提前引用的入口

↓

创建B时需要A

↓

提前拿到A

↓

B创建完成

↓

再注入A

↓

A创建完成

🟡 三级缓存

一级缓存 singletonObjects
= 创建完成的单例Bean

二级缓存 earlySingletonObjects
= 已经获取到的提前引用

三级缓存 singletonFactories
= 获取提前引用的ObjectFactory

速记:

一级:成品

二级:提前引用

三级:获取提前引用的工厂

🔴 最重要的流程

创建A

↓

实例化A

↓

三级缓存放入A的ObjectFactory

↓

A需要B

↓

创建B

↓

B需要A

↓

三级缓存获取A的提前引用

↓

A提前引用进入二级缓存

↓

A注入B

↓

B创建完成

↓

B注入A

↓

A创建完成

↓

进入一级缓存

📚 一、什么是循环依赖?

例如:

@Service
public class AService {

    @Autowired
    private BService bService;

}

B:

@Service
public class BService {

    @Autowired
    private AService aService;

}

关系:

A

↓

需要B

↓

B

↓

又需要A

这就是:

循环依赖。


二、为什么会出现死循环?

如果没有特殊处理:

创建A:

创建A

↓

A需要B

↓

创建B

↓

B需要A

↓

创建A

↓

A需要B

↓

创建B

↓

……

永远创建不完。

所以 Spring 必须想办法:

打破 A 等 B、B 又等 A 的循环。


三、Bean 创建不是一步完成的 ⭐⭐⭐⭐⭐

很多人容易认为:

new AService();

就代表 Bean 创建完成。

实际上 Spring 创建 Bean 大致经历:

实例化

↓

属性填充

↓

初始化

↓

Bean创建完成

例如:

AService a = new AService();

这时候:

A对象已经存在。

但是:

a.bService = null

因为:

B还没有注入。

所以:

对象已经实例化

≠

Bean已经完整创建

这就是 Spring 能解决循环依赖的关键。


四、什么叫提前暴露? ⭐⭐⭐⭐⭐

假设 Spring 正在创建 A。

先:

AService a = new AService();

此时:

A对象已经存在

但是

A还没有注入B

正常情况应该:

A全部创建完成

↓

再给别人使用

但是为了处理循环依赖:

Spring 会提前准备一个:

获取A提前引用的入口

如果后面创建 B 时:

B急着需要A

就可以:

提前拿到A

这就是:

提前暴露。


可以理解成:

A:

房子已经盖出来了

但是还没装修完成

↓

B现在急着要A

↓

先把钥匙给B

所以:

提前暴露并不是:

A已经完全创建完成

而是:

A已经实例化

但还没有完全初始化

五、三级缓存是什么? ⭐⭐⭐⭐⭐

Spring 单例 Bean 有三个重要缓存:


1. 一级缓存 singletonObjects

保存:

已经完全创建完成的单例Bean

例如:

singletonObjects

A → 完整A

B → 完整B

平时:

applicationContext.getBean("aService");

正常获取 Bean:

主要就是从这里拿。


2. 二级缓存 earlySingletonObjects

保存:

已经获取出来的提前引用

也就是:

Bean 还没有完全创建完成。

但是:

已经有人因为循环依赖需要它了。

例如:

earlySingletonObjects

A → A的提前引用

3. 三级缓存 singletonFactories

保存:

ObjectFactory

注意:

三级缓存里不是直接保存:

半成品A

而是保存:

获取 A 提前引用的工厂。

可以粗略理解:

singletonFactories

A → ObjectFactory

需要 A 时:

ObjectFactory

↓

获取A的提前引用

六、三级缓存怎么解决 A → B → A? ⭐⭐⭐⭐⭐

假设:

A依赖B

B依赖A

第一步:创建A

Spring:

AService a = new AService();

现在:

A已经实例化

但是:

A.bService = null

第二步:把A的ObjectFactory放入三级缓存

三级缓存:

A → ObjectFactory

意思:

如果后面有人提前需要 A,可以通过这个 ObjectFactory 获取 A 的提前引用。


第三步:给A注入B

Spring发现:

A需要B

但是:

B还不存在。

于是:

创建B

第四步:创建B

BService b = new BService();

然后:

Spring发现:

B需要A

问题来了:

A还没有创建完成。

怎么办?

开始查缓存。


第五步:查一级缓存

singletonObjects

有没有A?

没有 ❌

因为:

A还没创建完成。


第六步:查二级缓存

earlySingletonObjects

有没有A?

没有 ❌

因为:

A的提前引用还没有真正获取过。


第七步:查三级缓存

singletonFactories

发现:

A → ObjectFactory

有。

于是:

ObjectFactory

↓

获取A的提前引用

然后:

把这个提前引用放到:

二级缓存

变成:

earlySingletonObjects

A → A的提前引用

第八步:把A注入B

现在:

B终于拿到了A:

B.aService = A

所以:

B可以继续创建。

最终:

B创建完成

进入:

一级缓存

第九步:回去继续创建A

现在:

B已经创建完成。

所以:

A.bService = B

A也可以继续:

初始化

↓

创建完成

最终:

A进入:

一级缓存

七、完整流程图 ⭐⭐⭐⭐⭐

开始创建A
    ↓
new A()
    ↓
A已经实例化
    ↓
三级缓存放入A的ObjectFactory
    ↓
给A注入属性
    ↓
发现A需要B
    ↓
开始创建B
    ↓
new B()
    ↓
给B注入属性
    ↓
发现B需要A
    ↓
查一级缓存
    ↓
没有A
    ↓
查二级缓存
    ↓
没有A
    ↓
查三级缓存
    ↓
找到A的ObjectFactory
    ↓
获取A的提前引用
    ↓
放入二级缓存
    ↓
把A注入B
    ↓
B创建完成
    ↓
B进入一级缓存
    ↓
回到A
    ↓
把B注入A
    ↓
A创建完成
    ↓
A进入一级缓存

最终:

A → B

B → A

循环依赖解决。


八、为什么需要三级缓存?二级不够吗? ⭐⭐⭐⭐⭐

这是面试非常喜欢追问的。

如果没有 AOP:

理论上:

A实例化

↓

直接把A放二级缓存

也可以解决简单循环依赖。

但是:

Spring 还需要考虑:

AOP代理

例如:

@Transactional
public void save() {

}

A最终可能不是:

原始A

而是:

A的代理对象

↓

原始A

如果直接把原始A放到二级缓存:

可能出现:

B拿到:

原始A


其他地方拿到:

A的代理对象

这样:

可能出现 Bean 引用不一致。


所以 Spring 使用三级缓存:

A → ObjectFactory

真正有人提前需要 A 时:

ObjectFactory

↓

获取A的提前引用

↓

必要时处理AOP代理

↓

返回正确引用

所以:

三级缓存的重要作用之一,是在提前暴露 Bean 时,为 AOP 代理等处理保留机会。


九、三级缓存怎么记?

不要死背名字。

先记:

一级缓存

↓

成品仓库


二级缓存

↓

提前引用仓库


三级缓存

↓

获取提前引用的工厂

对应:

一级:

singletonObjects


二级:

earlySingletonObjects


三级:

singletonFactories

十、为什么构造器循环依赖解决不了? ⭐⭐⭐⭐⭐

例如:

public AService(BService bService) {

    this.bService = bService;

}

B:

public BService(AService aService) {

    this.aService = aService;

}

创建A:

new A(???)

↓

必须先有B

所以:

去创建B。

但是创建B:

new B(???)

↓

必须先有A

问题:

A还没有new出来

因此:

根本没有:

A的引用

可以提前暴露。


流程:

创建A

↓

需要B才能new A

↓

创建B

↓

需要A才能new B

↓

但是A还没创建

↓

死循环

所以:

构造器循环依赖通常无法通过三级缓存的提前暴露机制解决。


十一、为什么字段注入可以?

字段注入:

@Autowired
private BService bService;

可以:

先:

AService a = new AService();

再:

a.bService = b;

所以:

先实例化A

↓

A对象已经存在

↓

提前暴露A

↓

再给A注入B

有提前暴露的机会。


而构造器:

new A(b)

必须:

先有B。

所以:

没有提前暴露A的机会。


十二、Spring Boot 中要注意

即使理解了三级缓存:

也不要把循环依赖当成正常设计方式。

例如:

OrderService
    ↓
UserService
    ↓
OrderService

通常说明:

两个 Service:

职责可能耦合过重。

更好的方式可能是:

拆分公共逻辑

↓

新的Service

或者:

重新调整依赖关系。


🎤 面试回答(30秒)

Spring 主要通过三级缓存和提前暴露 Bean 引用来解决部分单例 Bean 的循环依赖。

Bean 创建分为实例化、属性填充和初始化等阶段。当 A 依赖 B、B 又依赖 A 时,Spring 在 A 实例化之后,会把获取 A 提前引用的 ObjectFactory 放入三级缓存。

创建 B 时如果需要 A,可以通过三级缓存获取 A 的提前引用,并放入二级缓存,然后完成 B 的创建,再把 B 注入 A,最终 A 和 B 创建完成后进入一级缓存。

三级缓存还为 AOP 代理等场景保留了处理提前引用的机会。


🎯 面试追问

Q1:什么是循环依赖?

答:

A依赖B

B又依赖A

Q2:Spring 怎么解决循环依赖?

答:

三级缓存

+

提前暴露Bean引用

Q3:什么叫提前暴露?

答:

Bean 已经实例化,但是还没有完全初始化时,提前提供它的引用给其他 Bean 使用。


Q4:三级缓存分别是什么?

答:

一级:

singletonObjects

完整Bean


二级:

earlySingletonObjects

提前引用


三级:

singletonFactories

获取提前引用的ObjectFactory

Q5:三级缓存里直接放半成品Bean吗?

答:

不是。

三级缓存保存的是:

ObjectFactory

通过它:

获取 Bean 的提前引用。


Q6:为什么需要三级缓存?

答:

为了在提前获取 Bean 引用时保留处理机会,例如 AOP 代理。


Q7:为什么构造器循环依赖解决不了?

答:

因为构造器必须先拿到依赖对象才能完成实例化,此时 Bean 自己都还没有 new 出来,没有引用可以提前暴露。


Q8:为什么字段注入可以解决部分循环依赖?

答:

因为可以先实例化 Bean,再进行属性注入,因此实例化后的 Bean 可以提前暴露。


⚠️ 工作踩坑

1. 主动设计循环依赖

虽然 Spring 在部分场景可以处理:

A → B → A

但是:

不代表这是好的设计。

循环依赖通常意味着:

Bean之间耦合过高

应该考虑重新拆分职责。


2. 认为所有循环依赖 Spring 都能解决

错误。

例如:

构造器循环依赖

通常无法通过提前暴露机制解决。


3. 认为三级缓存保存的是三个版本的Bean

错误。

三级缓存:

不是三个 Bean。

而是不同创建阶段使用的缓存:

一级:

完整Bean


二级:

提前引用


三级:

ObjectFactory

❓ 自测

  1. 什么是循环依赖?

  2. Spring 为什么可以解决部分循环依赖?

  3. 什么叫提前暴露?

  4. Bean 实例化完成是不是代表 Bean 已经创建完成?

  5. Spring 三级缓存分别是什么?

  6. 一级缓存放什么?

  7. 二级缓存放什么?

  8. 三级缓存放什么?

  9. 为什么三级缓存不是直接放半成品 Bean?

  10. A → B → A 的完整创建流程是什么?

  11. 为什么字段注入可以解决部分循环依赖?

  12. 为什么构造器循环依赖通常解决不了?

  13. 三级缓存和 AOP 有什么关系?


评论