Spring 如何解决循环依赖,网上的资料很多,但是感觉写得好的极少,特别是源码解读方面,我就自己单独出一篇,这篇文章绝对肝!
不 BB,上文章目录。
图片
1. 基础知识
1.1 什么是循环依赖 ?
一个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成一个环形调用,有下面 3 种方式。
图片
我们看一个简单的 Demo,对标“情况 2”。
@Service
public class Louzai1 {
@Autowired
private Louzai2 louzai2;
public void test1() {
}
}
@Service
public class Louzai2 {
@Autowired
private Louzai1 louzai1;
public void test2() {
}
}
这是一个经典的循环依赖,它能正常运行,后面我们会通过源码的角度,解读整体的执行流程。
1.2 三级缓存
解读源码流程之前,spring 内部的三级缓存逻辑必须了解,要不然后面看代码会蒙圈。
- 第一级缓存:singletonObjects,用于保存实例化、注入、初始化完成的 bean 实例;
- 第二级缓存:earlySingletonObjects,用于保存实例化完成的 bean 实例;
- 第三级缓存:singletonFactories,用于保存 bean 创建工厂,以便后面有机会创建代理对象。
这是最核心,我们直接上源码:
图片
执行逻辑:
- 先从“第一级缓存”找对象,有就返回,没有就找“二级缓存”;
- 找“二级缓存”,有就返回,没有就找“三级缓存”;
- 找“三级缓存”,找到了,就获取对象,放到“二级缓存”,从“三级缓存”移除。
1.3 原理执行流程
我把“情况 2”执行的流程分解为下面 3 步,是不是和“套娃”很像 ?
图片
整个执行逻辑如下:
为什么要用 3 级缓存 ?我们先看源码执行流程,后面我会给出答案。
2. 源码解读
注意:Spring 的版本是 5.2.15.RELEASE,否则和我的代码不一样!!!
上面的知识,网上其实都有,下面才是我们的重头戏,让你跟着楼仔,走一遍代码流程。
2.1 代码入口
图片
图片
这里需要多跑几次,把前面的 beanName 跳过去,只看 louzai1。
图片
图片
2.2 第一层
图片
进入 doGetBean(),从 getSingleton() 没有找到对象,进入创建 Bean 的逻辑。
图片
图片
进入 doCreateBean() 后,调用 addSingletonFactory()。
图片
往三级缓存 singletonFactories 塞入 louzai1 的工厂对象。
图片
图片
进入到 populateBean(),执行 postProcessProperties(),这里是一个策略模式,找到下图的策略对象。
图片
正式进入该策略对应的方法。
图片
下面都是为了获取 louzai1 的成员对象,然后进行注入。
图片
图片
图片
图片
进入 doResolveDependency(),找到 louzai1 依赖的对象名 louzai2
图片
需要获取 louzai2 的 bean,是 AbstractBeanFactory 的方法。
图片
正式获取 louzai2 的 bean。
图片
到这里,第一层套娃基本结束,因为 louzai1 依赖 louzai2,下面我们进入第二层套娃。
2.3 第二层
图片
获取 louzai2 的 bean,从 doGetBean(),到 doResolveDependency(),和第一层的逻辑完全一样,找到 louzai2 依赖的对象名 louzai1。
前面的流程全部省略,直接到 doResolveDependency()。
图片
正式获取 louzai1 的 bean。
图片
到这里,第二层套娃结束,因为 louzai2 依赖 louzai1,所以我们进入第三层套娃。
2.4 第三层
图片
获取 louzai1 的 bean,在第一层和第二层中,我们每次都会从 getSingleton() 获取对象,但是由于之前没有初始化 louzai1 和 louzai2 的三级缓存,所以获取对象为空。
图片
敲重点!敲重点!!敲重点!!!
到了第三层,由于第三级缓存有 louzai1 数据,这里使用三级缓存中的工厂,为 louzai1 创建一个代理对象,塞入二级缓存。
图片
这里就拿到了 louzai1 的代理对象,解决了 louzai2 的依赖关系,返回到第二层。
2.5 返回第二层
返回第二层后,louzai2 初始化结束,这里就结束了么?二级缓存的数据,啥时候会给到一级呢?
甭着急,看这里,还记得在 doGetBean() 中,我们会通过 createBean() 创建一个 louzai2 的 bean,当 louzai2 的 bean 创建成功后,我们会执行 getSingleton(),它会对 louzai2 的结果进行处理。
图片
我们进入 getSingleton(),会看到下面这个方法。
图片
这里就是处理 louzai2 的 一、二级缓存的逻辑,将二级缓存清除,放入一级缓存。
图片
2.6 返回第一层
同 2.5,louzai1 初始化完毕后,会把 louzai1 的二级缓存清除,将对象放入一级缓存。
图片
到这里,所有的流程结束,我们返回 louzai1 对象。
3. 原理深度解读
3.1 什么要有 3 级缓存 ?
这是一道非常经典的面试题,前面已经告诉大家详细的执行流程,包括源码解读,但是没有告诉大家为什么要用 3 级缓存?
这里是重点!敲黑板!!!
我们先说“一级缓存”的作用,变量命名为 singletonObjects,结构是 Map,它就是一个单例池,将初始化好的对象放到里面,给其它线程使用,如果没有第一级缓存,程序不能保证 Spring 的单例属性。
“二级缓存”先放放,我们直接看“三级缓存”的作用,变量命名为 singletonFactories,结构是 Map