
文章目录:

一. 基础知识
1.1 什么是循环依赖?
一个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成一个环形调用,有以下3种方式。

我们看一个简单的Demo,对标“情况2”。
@Service
public class Model1 {
@Autowired
private Model2 model2;
public void test1() {
}
}
@Service
public class Model2 {
@Autowired
private Model1 model1;
public void test2() {
}
}
这是一个经典的循环依赖,它能正常运行,后面我们会通过源码的角度,解读整体的执行流程。
1.2 三级缓存
解读源码流程之前,Spring框架内部的三级缓存逻辑必须了解,要不然后面看代码会蒙圈。

- 第一级缓存:
singletonObjects,用于保存实例化、注入、初始化完成的bean实例;
- 第二级缓存:
earlySingletonObjects,用于保存实例化完成的bean实例;
- 第三级缓存:
singletonFactories,用于保存bean创建工厂,以便后面有机会创建代理对象。

这是最核心的逻辑,我们直接看其抽象执行流程:

- 先从“第一级缓存”找对象,有就返回,没有就找“二级缓存”;
- 找“二级缓存”,有就返回,没有就找“三级缓存”;
- 找“三级缓存”,找到了,就获取对象,放到“二级缓存”,并从“三级缓存”移除。

1.3 原理执行流程
以“情况2”为例,其执行流程可以分解为以下3步:

整个执行逻辑如下:

- 在第一层中,先去获取A的Bean,发现没有就准备去创建一个,然后将A的代理工厂放入“三级缓存”(此时的A是一个半成品,尚未进行属性注入)。由于A依赖B,必须先去创建B。
- 在第二层中,准备创建B,发现B又依赖A,需要再去创建A。
- 在第三层中,去创建A,因为第一层已经创建了A的代理工厂,此时直接从“三级缓存”中拿到A的代理工厂,获取A的代理对象,放入“二级缓存”,并清除“三级缓存”。
- 回到第二层,现在有了A的代理对象,B的依赖得以解决(注入了半成品的A),B初始化成功。
- 回到第一层,现在B初始化成功,完成A对象的属性注入,然后继续填充A的其它属性,并执行A的后续初始化步骤(包括AOP),最终完成对A完整Bean的初始化。
- 将完整的A放入“一级缓存”。

为什么要用三级缓存?我们先跟踪源码执行流程,后面会给出详细解答。
二. 源码解读

注意:本文基于Spring 5.2.15.RELEASE版本,不同版本代码可能存在差异。

下面我们将通过Debug的方式,深入代码流程。
2.1 代码入口


调试时需要跳过前面的beanName,直接关注Model1的创建过程。


2.2 第一层

进入doGetBean()方法,从getSingleton()没有找到对象,进入创建Bean的逻辑。


进入doCreateBean()后,调用addSingletonFactory()方法。

此操作向三级缓存singletonFactories中放入model1的工厂对象。



进入populateBean(),执行postProcessProperties(),这里是一个策略模式,找到对应的策略对象。

正式进入该策略对应的注入方法。

下面的流程是为了获取model1的依赖对象(model2)并进行注入。




进入doResolveDependency(),找到model1依赖的对象名model2。

这里需要获取model2的bean,调用的是AbstractBeanFactory的方法。

正式发起获取model2 bean的请求。

至此,第一层逻辑因依赖model2而暂停,进入第二层。
2.3 第二层

获取model2的bean,从doGetBean()到doResolveDependency(),逻辑与第一层完全一致,最终找到model2依赖的对象名model1。
前面的流程省略,直接到doResolveDependency()。

正式发起获取model1 bean的请求。

至此,第二层逻辑因依赖model1而暂停,进入第三层。
2.4 第三层

再次获取model1的bean。在第一层和第二层中,每次都会从getSingleton()获取,但由于之前model1和model2的三级缓存皆为空,故获取对象为空。

关键点在此!
到达第三层时,由于三级缓存中已存在model1的工厂,这里会使用该工厂为model2创建一个代理对象,并将其放入二级缓存。

此时,model2成功获得了model1的代理对象,其依赖关系得以解决,流程返回到第二层。
2.5 返回第二层
返回第二层后,model2完成初始化。那么,二级缓存的数据何时会晋升到一级呢?
答案在doGetBean()中。当通过createBean()成功创建model2的bean后,会执行getSingleton()方法对结果进行处理。

进入getSingleton(),会看到以下方法。

这里就是处理model2的一、二级缓存的逻辑:将其从二级缓存清除,并放入一级缓存。

2.6 返回第一层
同理,model1初始化完毕后,也会通过相同的逻辑,将model1从二级缓存清除,并将完整的对象放入一级缓存。

至此,整个Bean的创建过程与依赖解决流程全部结束,最终返回初始化完成的model1对象。
三. 原理深度解读
3.1 为什么要有三级缓存?
这是一个非常经典的面试题。前面已经介绍了详细的执行流程和源码,现在我们来探讨设计三级缓存的原因。
核心解析:
- 一级缓存
singletonObjects:它是一个单例池,用于存放完全初始化好的Bean。它的存在是为了保证Spring容器的单例属性。如果没有它,无法确保全局唯一的单例。
- 三级缓存
singletonFactories:用于存放创建Bean的工厂(ObjectFactory)。它的核心作用是打破循环依赖。在循环依赖场景中,Bean A可能只完成实例化,还未进行属性注入(是一个“半成品”),就需要被Bean B引用。此时,就可以通过三级缓存中A的工厂,提前暴露这个半成品对象的引用(可能是原始对象,也可能是代理对象),从而让B能够继续初始化。
- 二级缓存
earlySingletonObjects:用于存放从三级缓存的工厂中获取到的对象。它的存在主要是为了支持AOP。如果没有二级缓存,考虑一个更复杂的场景:Bean A被Bean B和Bean C同时循环依赖,且A需要AOP代理。那么B和C在通过三级缓存获取A时,工厂每次都会生成一个新的代理对象(A1和A2),这就破坏了单例原则。二级缓存存储了第一次从工厂获取的对象,后续再需要时直接从此获取,保证了唯一性。
通过查看工厂方法的源码,可以更清晰地理解其作用。在getEarlyBeanReference()方法中:


该工厂的核心逻辑是:

- 如果当前Bean有AOP切面,就为其创建一个代理对象返回。
- 如果当前Bean没有AOP切面,则直接返回原始对象。

3.2 能干掉第二级缓存吗?
考虑以下场景:
@Service
public class A {
@Autowired private B b;
@Autowired private C c;
// A 需要AOP代理
}
@Service
public class B {
@Autowired private A a;
}
@Service
public class C {
@Autowired private A a;
}
如果只有一级和三级缓存:

- B依赖A,通过三级缓存工厂生成代理对象A1。
- C依赖A,通过三级缓存工厂生成代理对象A2。

此时,A1和A2是两个不同的代理对象,违反了单例原则。因此,二级缓存的目的就是为了避免因AOP创建多个代理对象,它存储的是半成品的AOP单例Bean。如果没有AOP,理论上只需要一级和三级缓存即可。
四. 总结
最后,我们回顾一下Spring三级缓存各自的核心使命:

- 一级缓存 (
singletonObjects):为“Spring的单例属性”而生,是完整的单例Bean的家。
- 二级缓存 (
earlySingletonObjects):为“解决AOP代理对象的唯一性”而生,存放半成品的AOP单例Bean。
- 三级缓存 (
singletonFactories):为“打破循环依赖”而生,存放用于生成半成品Bean的工厂方法。

理解这三条,就意味着对Spring循环依赖的核心原理有了比较透彻的掌握。本文旨在梳理核心原理并引导读者通过Debug理解流程。阅读源码时建议:掌握基本设计模式、预先了解理论知识、善用英文注释,最重要的是在Debug时保持全局观,聚焦核心逻辑,避免陷入过多细节。