0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

什么是循环依赖?

倩倩 来源:楼仔 作者:楼仔 2022-09-08 10:49 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群


7d76c324-2f1f-11ed-ba43-dac502259ad0.png

1. 基础知识

1.1 什么是循环依赖 ?

一个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成一个环形调用,有下面 3 种方式。

7d9ea39e-2f1f-11ed-ba43-dac502259ad0.png

我们看一个简单的 Demo,对标“情况 2”。

@Service
publicclassLouzai1{

@Autowired
privateLouzai2louzai2;

publicvoidtest1(){
}
}

@Service
publicclassLouzai2{
@Autowired
privateLouzai1louzai1;

publicvoidtest2(){
}
}

这是一个经典的循环依赖,它能正常运行,后面我们会通过源码的角度,解读整体的执行流程。

1.2 三级缓存

解读源码流程之前,spring 内部的三级缓存逻辑必须了解,要不然后面看代码会蒙圈。

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

这是最核心,我们直接上源码:

7db71186-2f1f-11ed-ba43-dac502259ad0.png

执行逻辑:

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

1.3 原理执行流程

我把“情况 2”执行的流程分解为下面 3 步,是不是和“套娃”很像 ?

7de0ad48-2f1f-11ed-ba43-dac502259ad0.png

整个执行逻辑如下:

  1. 在第一层中,先去获取 A 的 Bean,发现没有就准备去创建一个,然后将 A 的代理工厂放入“三级缓存”(这个 A 其实是一个半成品,还没有对里面的属性进行注入 ),但是 A 依赖 B 的创建,就必须先去创建 B;
  2. 在第二层中,准备创建 B,发现 B 又依赖 A,需要先去创建 A;
  3. 在第三层中,去创建 A,因为第一层已经创建了 A 的代理工厂,直接从“三级缓存”中拿到 A 的代理工厂,获取 A 的代理对象,放入“二级缓存” ,并清除“三级缓存”;
  4. 回到第二层,现在有了 A 的代理对象,对 A 的依赖完美解决(这里的 A 仍然是个半成品 ),B 初始化成功;
  5. 回到第一层,现在 B 初始化成功,完成 A 对象的属性注入,然后再填充 A 的其它属性,以及 A 的其它步骤(包括 AOP),完成对 A 完整的初始化功能(这里的 A 才是完整的 Bean )。
  6. 将 A 放入“一级缓存”。

为什么要用 3 级缓存 ?我们先看源码执行流程,后面我会给出答案。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

2. 源码解读

注意:Spring 的版本是 5.2.15.RELEASE ,否则和我的代码不一样!!!

上面的知识,网上其实都有,下面才是我们的重头戏,让你跟着楼仔,走一遍代码流程。

2.1 代码入口

7e0d204e-2f1f-11ed-ba43-dac502259ad0.png7e326a16-2f1f-11ed-ba43-dac502259ad0.png

这里需要多跑几次,把前面的 beanName 跳过去,只看 louzai1。

7e50616a-2f1f-11ed-ba43-dac502259ad0.png7e72d556-2f1f-11ed-ba43-dac502259ad0.png

2.2 第一层

7e905a36-2f1f-11ed-ba43-dac502259ad0.png

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

7eb0d770-2f1f-11ed-ba43-dac502259ad0.png7ecdbbb0-2f1f-11ed-ba43-dac502259ad0.png

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

7ef65eb2-2f1f-11ed-ba43-dac502259ad0.png

往三级缓存 singletonFactories 塞入 louzai1 的工厂对象。

7f17fe3c-2f1f-11ed-ba43-dac502259ad0.png7f52c4a4-2f1f-11ed-ba43-dac502259ad0.png

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

7f71d3a8-2f1f-11ed-ba43-dac502259ad0.png

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

7f9b11b4-2f1f-11ed-ba43-dac502259ad0.png

下面都是为了获取 louzai1 的成员对象,然后进行注入。

7fa65650-2f1f-11ed-ba43-dac502259ad0.png7fd14914-2f1f-11ed-ba43-dac502259ad0.png7fed7fd0-2f1f-11ed-ba43-dac502259ad0.png7ff82c0a-2f1f-11ed-ba43-dac502259ad0.png

进入 doResolveDependency(),找到 louzai1 依赖的对象名 louzai2

8008ff76-2f1f-11ed-ba43-dac502259ad0.png

需要获取 louzai2 的 bean,是 AbstractBeanFactory 的方法。

8018f020-2f1f-11ed-ba43-dac502259ad0.png

正式获取 louzai2 的 bean。

8034f6b2-2f1f-11ed-ba43-dac502259ad0.png

到这里,第一层套娃基本结束,因为 louzai1 依赖 louzai2,下面我们进入第二层套娃。

2.3 第二层

804c28dc-2f1f-11ed-ba43-dac502259ad0.png

获取 louzai2 的 bean,从 doGetBean(),到 doResolveDependency(),和第一层的逻辑完全一样,找到 louzai2 依赖的对象名 louzai1。

前面的流程全部省略,直接到 doResolveDependency()。

806a87fa-2f1f-11ed-ba43-dac502259ad0.png

正式获取 louzai1 的 bean。

80776cea-2f1f-11ed-ba43-dac502259ad0.png

到这里,第二层套娃结束,因为 louzai2 依赖 louzai1,所以我们进入第三层套娃。

2.4 第三层

809c6b8a-2f1f-11ed-ba43-dac502259ad0.png

获取 louzai1 的 bean,在第一层和第二层中,我们每次都会从 getSingleton() 获取对象,但是由于之前没有初始化 louzai1 和 louzai2 的三级缓存,所以获取对象为空。

80c2d7c0-2f1f-11ed-ba43-dac502259ad0.png80e2b93c-2f1f-11ed-ba43-dac502259ad0.png

敲重点!敲重点!!敲重点!!!

到了第三层,由于第三级缓存有 louzai1 数据,这里使用三级缓存中的工厂,为 louzai1 创建一个代理对象,塞入二级缓存。

80fca392-2f1f-11ed-ba43-dac502259ad0.png

这里就拿到了 louzai1 的代理对象,解决了 louzai2 的依赖关系,返回到第二层。

2.5 返回第二层

返回第二层后,louzai2 初始化结束,这里就结束了么?二级缓存的数据,啥时候会给到一级呢?

甭着急,看这里,还记得在 doGetBean() 中,我们会通过 createBean() 创建一个 louzai2 的 bean,当 louzai2 的 bean 创建成功后,我们会执行 getSingleton(),它会对 louzai2 的结果进行处理。

812f996e-2f1f-11ed-ba43-dac502259ad0.png

我们进入 getSingleton(),会看到下面这个方法。

813aad22-2f1f-11ed-ba43-dac502259ad0.png

这里就是处理 louzai2 的 一、二级缓存的逻辑,将二级缓存清除,放入一级缓存。

815da99e-2f1f-11ed-ba43-dac502259ad0.png

2.6 返回第一层

同 2.5,louzai1 初始化完毕后,会把 louzai1 的二级缓存清除,将对象放入一级缓存。

818a0aac-2f1f-11ed-ba43-dac502259ad0.png

到这里,所有的流程结束,我们返回 louzai1 对象。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

3. 原理深度解读

3.1 什么要有 3 级缓存 ?

这是一道非常经典的面试题,前面已经告诉大家详细的执行流程,包括源码解读,但是没有告诉大家为什么要用 3 级缓存?

这里是重点!敲黑板!!!

我们先说“一级缓存”的作用,变量命名为 singletonObjects,结构是 Map,它就是一个单例池,将初始化好的对象放到里面,给其它线程使用,如果没有第一级缓存,程序不能保证 Spring 的单例属性。

“二级缓存”先放放,我们直接看“三级缓存”的作用,变量命名为 singletonFactories,结构是 Map>,Map 的 Value 是一个对象的代理工厂,所以“三级缓存”的作用,其实就是用来存放对象的代理工厂。

那这个对象的代理工厂有什么作用呢,我先给出答案,它的主要作用是存放半成品的单例 Bean,目的是为了“打破循环” ,可能大家还是不太懂,这里我再稍微解释一下。

我们回到文章开头的例子,创建 A 对象时,会把实例化的 A 对象存入“三级缓存”,这个 A 其实是个半成品,因为没有完成 A 的依赖属性 B 的注入,所以后面当初始化 B 时,B 又要去找 A,这时就需要从“三级缓存”中拿到这个半成品的 A(这里描述,其实也不完全准确,因为不是直接拿,为了让大家好理解,我就先这样描述),打破循环。

那我再问一个问题,为什么“三级缓存”不直接存半成品的 A,而是要存一个代理工厂呢 ?答案是因为 AOP。

在解释这个问题前,我们看一下这个代理工厂的源码,让大家有一个更清晰的认识。

直接找到创建 A 对象时,把实例化的 A 对象存入“三级缓存”的代码,直接用前面的两幅截图。

7ef65eb2-2f1f-11ed-ba43-dac502259ad0.png7f17fe3c-2f1f-11ed-ba43-dac502259ad0.png

下面我们主要看这个对象工厂是如何得到的,进入 getEarlyBeanReference() 方法。

81e4a674-2f1f-11ed-ba43-dac502259ad0.png8200c28c-2f1f-11ed-ba43-dac502259ad0.png8212d3fa-2f1f-11ed-ba43-dac502259ad0.png822e74d4-2f1f-11ed-ba43-dac502259ad0.png

最后一幅图太重要了,我们知道这个对象工厂的作用:

  • 如果 A 有 AOP,就创建一个代理对象;
  • 如果 A 没有 AOP,就返回原对象。

那“二级缓存”的作用就清楚了,就是用来存放对象工厂生成的对象,这个对象可能是原对象,也可能是个代理对象。

我再问一个问题,为什么要这样设计呢?把二级缓存干掉不行么 ?我们继续往下看。

3.2 能干掉第 2 级缓存么 ?

@Service
publicclassA{

@Autowired
privateBb;

@Autowired
privateCc;

publicvoidtest1(){
}
}

@Service
publicclassB{
@Autowired
privateAa;

publicvoidtest2(){
}
}

@Service
publicclassC{

@Autowired
privateAa;

publicvoidtest3(){
}
}

根据上面的套娃逻辑,A 需要找 B 和 C,但是 B 需要找 A,C 也需要找 A。

假如 A 需要进行 AOP ,因为代理对象每次都是生成不同的对象,如果干掉第二级缓存,只有第一、三级缓存:

  • B 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A1。
  • C 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A2。

看到问题没?你通过 A 的工厂的代理对象,生成了两个不同的对象 A1 和 A2 ,所以为了避免这种问题的出现,我们搞个二级缓存,把 A1 存下来,下次再获取时,直接从二级缓存获取,无需再生成新的代理对象。

所以“二级缓存”的目的是为了避免因为 AOP 创建多个对象,其中存储的是半成品的 AOP 的单例 bean。

如果没有 AOP 的话,我们其实只要 1、3 级缓存,就可以满足要求。

4. 写在最后

我们再回顾一下 3 级缓存的作用:

  • 一级缓存:为“Spring 的单例属性”而生 ,就是个单例池,用来存放已经初始化完成的单例 Bean;
  • 二级缓存:为“解决 AOP”而生 ,存放的是半成品的 AOP 的单例 Bean;
  • 三级缓存:为“打破循环”而生 ,存放的是生成半成品单例 Bean 的工厂方法。

如果你能理解上面我说的三条,恭喜你,你对 Spring 的循环依赖理解得非常透彻!

关于循环依赖的知识,其实还有,因为篇幅原因,我就不再写了,这篇文章的重点,一方面是告诉大家循环依赖的核心原理,另一方面是让大家自己去 debug 代码 ,跑跑流程,挺有意思的。

可能有同学会问 “楼哥,你之前是不是经常看源码,然后这个流程,你是不是 debug 了很久?”

我之前其实没怎么看过开源代码,这个流程,前期理论知识看了 2.5 个小时,然后 debug 4.5 小时,就基本全部走通了,最难的地方,就是三层套娃,稍微有些绕。

这里也简单说一下我看源码的心得:

  1. 需要掌握基本的设计模式;
  2. 看源码前,最好能找一些理论知识先看看;
  3. 学会读英文注释,不会的话就百度翻译;
  4. debug 时,要克制自己,不要陷入无用的细节 ,这个最重要。

其中最难的是第 4 步,因为很多同学看 Spring 源码,每看一个方法,就想多研究研究,这样很容易被绕进去了,这个要学会克制,有大局观,并能分辨哪里是核心逻辑 ,至于如何分辨,可以在网上先找些资料,如果没有的话,就只能多看代码了。

今天的源码解析就到这了~


审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 源码
    +关注

    关注

    8

    文章

    682

    浏览量

    31098
  • spring
    +关注

    关注

    0

    文章

    341

    浏览量

    15782

原文标题:痛快!SpringBoot终于帮我们禁止了Spring循环依赖!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    高低温循环测试对电子元件寿命有什么影响

    在电子产品无处不在的今天,微小元件的可靠性直接关系整个系统的成败。小到手机,大到汽车、医疗及工业设备,任何元件的失效都可能造成设备瘫痪。要预知元件寿命,高低温循环测试是关键所在。什么是高低温循环测试
    的头像 发表于 10-16 15:00 324次阅读
    高低温<b class='flag-5'>循环</b>测试对电子元件寿命有什么影响

    人工智能行业如何使用for循环语句进行循环

    人工智能行业可以使用以下是关于for循环在不同编程语言中的基本用法说明: Python中的for循环: 主要用于遍历序列(列表、元组、字符串等) 典型结构:for item in sequence
    的头像 发表于 09-10 12:55 396次阅读

    高精度电流控制:端子电流循环寿命试验机的电子系统设计

    端子电流循环寿命试验机的电子系统设计,需在高精度电流控制的基础上,重点应对电流循环过程中的动态变化,满足循环模式的多样性、电流切换的平滑性以及长期运行的稳定性要求,以精准模拟端子在实际使用中的电流
    的头像 发表于 08-07 11:24 471次阅读
    高精度电流控制:端子电流<b class='flag-5'>循环</b>寿命试验机的电子系统设计

    工业循环冷却水智能管理系统方案

    在工业循环冷却水系统中,水质处理自动化包括自动加药和自动排污两大部分。其中,加药量是根据循环水体中药剂浓度测量值反馈进行控制的。特别是对冶金、石化等大型工厂来说,由于工业循环冷却水的用量很大,消耗
    的头像 发表于 08-07 10:37 724次阅读
    工业<b class='flag-5'>循环</b>冷却水智能管理系统方案

    ArkUI-X跨平台应用改造指南

    共通UI抽象并实现,实现代码复用的效果。 4.应注意,features层应合理设计模块,谨慎处理模块间依赖关系,避免循环依赖等问题。 模块main Products层harmonyos.hap(下面简称
    发表于 06-16 23:05

    前端开发中依赖包有问题怎么办

    在前端开发中,如果你发现某个依赖包存在问题,可以考虑以下步骤来解决: 一、简单方案 1. 检查问题来源 : 确认问题是否由依赖包引起,而不是你的代码或其他配置问题。 查看错误信息、文档和相关
    的头像 发表于 06-10 11:31 299次阅读

    深入理解C语言:C语言循环控制

    在C语言编程中,循环结构是至关重要的,它可以让程序重复执行特定的代码块,从而提高编程效率。然而,为了避免程序进入无限循环,C语言提供了多种循环控制语句,如break、continue和goto,用于
    的头像 发表于 04-29 18:49 1740次阅读
    深入理解C语言:C语言<b class='flag-5'>循环</b>控制

    技术干货驿站 ▏深入理解C语言:嵌套循环循环控制的底层原理

    大家好!在上一节中,我们学习了C语言中的基本循环语句,如for、while和do...while循环。今天,我们将进一步探讨嵌套循环循环控制,这些技巧可以帮助我们实现更复杂的逻辑操作
    的头像 发表于 02-21 18:26 1045次阅读
    技术干货驿站  ▏深入理解C语言:嵌套<b class='flag-5'>循环</b>与<b class='flag-5'>循环</b>控制的底层原理

    稳定性建设之依赖设计

    作者:京东物流 冯志文 背景 随着分布式微服务的发展,一个普通的应用可能会依赖于许多其他服务,这给系统的限流降级、优化改造等操作带来了困难。在没有明确强弱依赖关系的情况下,我们很难有效地进行这些操作
    的头像 发表于 02-21 09:49 700次阅读
    稳定性建设之<b class='flag-5'>依赖</b>设计

    火语言如何循环读取表格

    描述 从MySQL读取数据(包含列名:id,name,count,create_date)输出到表格类型变量dt,用For循环读取表格每行数据,通过dt.Rows[i]['id']取表格第i行列
    的头像 发表于 02-07 15:11 518次阅读
    火语言如何<b class='flag-5'>循环</b>读取表格

    精密空调制冷循环系统维护攻略

    精密空调作为现代数据中心、实验室等重要场所的关键设备,其制冷循环系统的稳定运行直接关系到整个环境的温湿度控制和设备的正常运行。因此,定期对精密空调的制冷循环系统进行维护,是确保其高效、稳定、安全运行的重要保障。
    的头像 发表于 02-06 17:27 725次阅读
    精密空调制冷<b class='flag-5'>循环</b>系统维护攻略

    汽轮机热力循环分析

    汽轮机热力循环是热力工程中的重要部分,以下是对其进行的分析: 一、热力循环概述 热力循环是指工质从某一状态点开始,经过一系列状态变化又回到原来这一状态点的封闭变化过程。在这个过程中,工质会经
    的头像 发表于 02-06 16:52 1763次阅读

    可靠性温度循环试验至少需要几个循环

    温度循环作为自然环境的模拟,可以考核产品在不同环境条件下的适应能力,常用于产品在开发阶段的型式试验、元器件的筛选试验。一、温度循环测试介绍温度循环试验,也称为热循环试验、高低温
    的头像 发表于 01-23 15:26 996次阅读
    可靠性温度<b class='flag-5'>循环</b>试验至少需要几个<b class='flag-5'>循环</b>?

    Simcenter Micred Power Tester功率循环测试仪

    SimcenterMicredPowerTester功率循环测试仪使用结合了有效功率循环和热结构退化监测的测试硬件,评估功率半导体的热可靠性和使用寿命。为什么选择
    的头像 发表于 01-09 14:33 1289次阅读
    Simcenter Micred Power Tester功率<b class='flag-5'>循环</b>测试仪

    电力风电场的可循环使用接地装置

    电子发烧友网站提供《电力风电场的可循环使用接地装置.docx》资料免费下载
    发表于 12-16 15:05 0次下载