来聊聊Spring的循环依赖

文章目录

  • 首先了解一下什么是循环依赖
  • 简述解决循环依赖全过程
    • 通过debug了解Spring解决循环依赖全过程
    • Aservice的创建
    • 递归来到Bservice的创建
    • 然后BService递归回到了getAservice的doGetBean中
    • 故事再次回到Aservice填充BService的步骤
  • 总结成流程图
  • 为什么二级就能解决循环依赖问题,而我们却要用三级缓存解决循环依赖问题呢
  • 循环依赖思想在生活中的运用

首先了解一下什么是循环依赖

如下代码所示,我们编写了一个A对象,A对象在new的时候要new一个B对象。
而我们new的B时,它会去new一个A对象,两者new的时候互相依赖,来来回回,就造成了大名鼎鼎的循环依赖问题。

public class ABTest {public static void main(String[] args) {new ClazzA();}}class ClazzA {private ClazzB b = new ClazzB();}class ClazzB {private ClazzA a = new ClazzA();}

简述解决循环依赖全过程

在debug之前,我们不妨了解一下,要解决循环依赖,那么代码该怎么写?其实计算机设计中就有一个不错的思想。觉得顶不住的时候,加个缓存试试看。
所以循环依赖问题也是同理。

如下代码所示,Obj1和Obj2是两个互相依赖的类,我们在创建对象Obj1时,不妨在他new的时候先将其放到缓存中。然后他发现它依赖Obj2,我们递归再去new Obj2,然后Obj2填充属性的时候发现他依赖Obj1,于是先去缓存中看看有没有Obj1有的话填充上去(虽然这时候Obj1还是个半成品,但是先解决燃眉之急要紧),然后代码递归回到Obj1填充Obj2的代码段,完成Obj1属性填充。

/*** 循环依赖解决的示例代码*/
public class CircleDepSolution {private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);private static <T> T getBean(Class<T> beanClass) throws Exception {//拿到这个bean的小写String beanName = beanClass.getSimpleName().toLowerCase();//去map中捞看看有没有if (singletonObjects.containsKey(beanName)) {return (T) singletonObjects.get(beanName);}//没有就自己去创建一个,并且塞到map中T obj = beanClass.newInstance();singletonObjects.put(beanName, obj);//然后捞出这个bean的所有成员属性Field[] fields = obj.getClass().getDeclaredFields();for (Field field : fields) {field.setAccessible(true);Class<?> filedCLass = field.getType();String filedName = filedCLass.getSimpleName().toLowerCase();//去map中捞,如果有就set,没有就递归调用一下再setfield.set(obj, singletonObjects.containsKey(filedName) ? singletonObjects.get(filedName) : getBean(filedCLass));}//最终再返回这个beanreturn  obj;}public static void main(String[] args) throws Exception{System.out.println(getBean(Obj1.class).getObj2());System.out.println(getBean(Obj2.class).getObj1());}
}

这种方式虽然解决了问题,但是我们却发现这种方案的一个特点,依赖的只有set方式的情况下才能解决,因为set使得依赖对象的创建和new分为两步骤,流程可以把控,我们完全可以先搞个半成品放到缓存中给别人取

通过debug了解Spring解决循环依赖全过程

首先编写循环依赖的对象AService和BService

@Service("aService")
public class AService {@Autowiredprivate BService bService;public BService getbService() {return bService;}public void setbService(BService bService) {this.bService = bService;}
}
@Service("bService")
public class BService {@Autowiredprivate AService aService;public AService getaService() {return aService;}public void setaService(AService aService) {this.aService = aService;}
}

然后我们开始debug

Aservice的创建

首先我们在预实例化的方法里面看到Aservice的beanName
在这里插入图片描述然后我们回去尝试拿这个bean对象
在这里插入图片描述点入发现真正做事的doGetBean方法

在这里插入图片描述可以看到doGetBean方法,先会调用一个getSingleton的方法,我们点入这个方法debug时候发现他不过是从一级缓存(即存放完全体的bean容器)是狗有aService,若没有且这个bean正处于创建中就执行创建并返回。若不存在且也没在创建中,那么就返回空对象

在这里插入图片描述在这里插入图片描述我们接着往下走,至于看到一个创建bean的核心逻辑,它会判断这个bean是不是单例的,若是则传beanName和一个创建bean的lambda表示式到getSingleton中。

在这里插入图片描述

我们点入时发现他的核心调用singletonFactory.getObject()

在这里插入图片描述这个方法就会执行上一步传入的lambda的createBean,而createBean会调用doCreateBean
在这里插入图片描述

然后完成bean的创建
在这里插入图片描述然后再判断这个bean是否不是完全体,若不是则放到三级缓存中

在这里插入图片描述

在这里插入图片描述
放到三级缓存后在进行属性填充

在这里插入图片描述
而在调用属性填充过程中,我们发现一个和自动注入相关的bean后置处理

在这里插入图片描述可以看到他在尝试着将BService注入

在这里插入图片描述
点入就发现注入的核心逻辑

在这里插入图片描述
注入回去beanFactory拿到建议的bean,然后点入我们发现核心的do逻辑
在这里插入图片描述在这里插入图片描述点入do方法后我们发现它内部调用了一个findValue,返回了一个null

在这里插入图片描述

在这里插入图片描述

这时候他就会去解决循环依赖问题,从bean容器中寻找候选人
在这里插入图片描述
在这里插入图片描述
有一次的调用了getBean
在这里插入图片描述

递归来到Bservice的创建

可以发现我们以递归的方式回到了原点,只不过这次是替Aservice找Bservice

在这里插入图片描述
一顿和Aservice差不多的操作后又来到属性填充的环节
在这里插入图片描述
然后有一次来到解决循环依赖的环节

在这里插入图片描述

然后BService递归回到了getAservice的doGetBean中

在这里插入图片描述

这时候容器发现Aservice被放在三级容器中处于被创建中的状态

在这里插入图片描述然后他就会调用早期的存到三级缓存中的lambda搞出Aservice
在这里插入图片描述
在这里插入图片描述然后将其存到二级缓存中

在这里插入图片描述
自此我们解决Bservice循环依赖的Aservice的问题,就是将一个半成品的Aservice给Bservice先用着
在这里插入图片描述
自此Bservice成为完全体

在这里插入图片描述这时候Bservice就会被放到一级缓存中

在这里插入图片描述

故事再次回到Aservice填充BService的步骤

自此aService也可以在一级缓存中找到Bservice解决了循环依赖问题

在这里插入图片描述

总结成流程图

为什么二级就能解决循环依赖问题,而我们却要用三级缓存解决循环依赖问题呢

循环依赖思想在生活中的运用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/226988.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Qt开发流程】之UDP

概述 UDP (User Datagram Protocol)是一种简单的传输层协议。与TCP不同&#xff0c;UDP不提供可靠的数据传输和错误检测机制。UDP主要用于那些对实时性要求较高、对数据传输可靠性要求较低的应用&#xff0c;如音频、视频、实时游戏等。 UDP使用无连接的数据报传输模式。在传…

如何实现订单自动取消

由于Redis具有过期监听的功能&#xff0c;于是就有人拿它来实现订单超时自动关闭的功能&#xff0c;但是这个方案并不完美。今天来聊聊11种实现订单超时自动关闭的方案&#xff0c;总有一种适合你&#xff01;这些方案并没有绝对的好坏之分&#xff0c;只是适用场景的不大相同。…

图的搜索(二):贝尔曼-福特算法、狄克斯特拉算法和A*算法

图的搜索&#xff08;二&#xff09;&#xff1a;贝尔曼-福特算法、狄克斯特拉算法和A*算法 贝尔曼-福特算法 贝尔曼-福特&#xff08;Bellman-Ford&#xff09;算法是一种在图中求解最短路径问题的算法。最短路径问题就是在加权图指定了起点和终点的前提下&#xff0c;寻找从…

Vue3使用了Vite和UnoCSS导致前端项目启动报错:Error:EMFILE:too many open files

一个 Vue3 的项目&#xff0c;用的是 Vite 打包&#xff0c;通过 npm run dev 运行时&#xff0c;遇到了以下错误&#xff08;尤其是引入了 Element-Plus 后&#xff09;&#xff1a; Error: EMFILE: too many open files&#xff0c;后面是具体的文件路径。。甚至到了 node_mo…

5G工业物联网网关,比4G工业网关强在哪里?

​随着5G技术的广泛应用&#xff0c;越来越多的行业开始探索如何利用5G网络提升效率和创新能力。其中&#xff0c;工业物联网领域是受益最大的领域之一。作为连接物联网设备和网络的关键组件&#xff0c;5G工业物联网网关在这个变革中发挥着至关重要的作用。本文将深入探讨5G工…

指针进阶篇

指针的基本概念&#xff1a; 指针是一个变量&#xff0c;对应内存中唯一的一个地址指针在32位平台下的大小是4字节&#xff0c;在64位平台下是8字节指针是有类型的&#xff0c;指针类型决定该指针的步长&#xff0c;即走一步是多长指针运算&#xff1a;指针-指针表示的是两个指…

赛氪为第五届全球校园人工智能算法精英大赛决赛选手保驾护航

12月10日&#xff0c;以“智青春算未来”为主题的2023年第五届全球校园人工智能算法精英大赛全国总决赛在河海大学江宁校区举行。本次大赛由江苏省人工智能学会主办&#xff0c;自9月份启动以来&#xff0c;共吸引了全国近400所高校的3000多支参赛团队参加。经过校赛、省赛选拔…

nlp与cv的发展

Transformer的出现,促进了更高容量模型的建立,为大模型的出现奠定基础. &#x1f9d0;大模型通常具有十亿个以上参数(仅供参考) &#x1f62e;左边的蓝色是CV领域、右下绿色是NLP、右上蓝色是多模态&#x1f603;基础模型(Foundational Models)首次由Bommasani等人在《Stanford…

HTTP代理服务器脚本录制

1、报错1 target controller is configured to “use recording Controller“ but no such controller exists,ensure_target controller is configured to "use recording -CSDN博客

等等Domino 14.0FP1

大家好&#xff0c;才是真的好。 节奏确实太快了&#xff0c;有时候我深感我也追不上。 以前Notes Domino是三年磨一剑&#xff0c;也就说每三年才发一个大版本&#xff0c;从2019年开始&#xff0c;进行了高频提速&#xff0c;居然一年一个大版本&#xff01; 周末&#xf…

NAT——网络地址转换

目录 一、概念 二、NAT的分类 1.静态NAT 1.1 静态NAT的配置 1.2 利用eNSP小实验加强对静态NAT的理解 2、动态NAT 三、NAPT——端口映射 四、Easy IP 使用一个公网地址可以让所有人都上公网 一、概念 随着Internet的发展和网络应用的增多&#xff0c;IPv4地址枯竭已经成为…

jmeter 如何循环使用接口返回的多值?

有同学在用jmeter做接口测试的时候&#xff0c;经常会遇到这样一种情况&#xff1a; 就是一个接口请求返回了多个值&#xff0c;然后下一个接口想循环使用前一个接口的返回值。 这种要怎么做呢&#xff1f; 有一定基础的人&#xff0c;可能第一反应就是先提取前一个接口返回…

在Node.js中MongoDB排序的方法

本文主要介绍在Node.js中MongoDB排序的方法。 目录 Node.js中MongoDB排序使用原生的mongodb驱动程序进行排序使用Mongoose库中的排序 Node.js中MongoDB排序 在Node.js中使用MongoDB进行排序&#xff0c;可以使用原生的mongodb驱动程序或者Mongoose库。 使用原生的mongodb驱动…

改进lora-scripts,支持SDXL训练,以及启动脚本

分享下自己改进的一个lora训练脚本&#xff0c;在ubuntu下如果SD-WEBUI的环境已经搭好的话&#xff0c;只需要下载lora-script就可以支持训练了&#xff0c;直接命令行方式训练。 首先&#xff0c;我们需要克隆下项目&#xff1a; git clone https://github.com/Akegarasu/lo…

黑色翻页时钟HTML源码-倒计时单页翻页时钟

黑色翻页时钟HTML源码-倒计时单页翻页时钟这是一个类似fliqlo的黑色翻页时钟HTML源码&#xff0c;它仅包含一个HTML文件&#xff0c;上传到网站后即可使用。该时钟具有查看当前时间、秒表和倒计时功能&#xff0c;并且可以在页面的右下角进行设置。 红色动态炫酷数字时钟html网…

【已解决】在使用poi-tl生成的word文档时候,怎么添加目录?poi-tl生成目录解决办法

需求&#xff1a; 需求的报告模板中大概包括标题、目录、前言、章节&#xff08;根据模板动态生成的标题文字表格图片&#xff09;&#xff0c;其中目录需要根据章节的实际情况动态生成。在网上没有找到什么好的解决方案&#xff0c;请教一下实现思路&#xff0c;非常感谢。 …

MATLAB 计算两片点云间的最小距离(2种方法) (39)

MATLAB 计算两片点云间的最小距离 (39) 一、算法介绍二、算法实现1.常规计算方法2.基于KD树的快速计算一、算法介绍 假设我们现在有两片点云 1 和 2 ,需要计算二者之间的最小距离,这里提供两种计算方法,分别是常规计算和基于KD树近邻搜索的快速计算方法,使用的测试数据如…

为什么选择国产WordPress:HelpLook的优势解析

如今网站建设可以说已经是企业必备。而在众多的网站建设工具中&#xff0c;WordPress无疑是其中的佼佼者。作为一款开源的CMS&#xff08;内容管理系统&#xff09;&#xff0c;WordPress拥有丰富的插件和主题&#xff0c;以及强大的功能&#xff0c;使得用户可以轻松地构建出符…

vivado约束方法8

无交互的逻辑互斥时钟组 逻辑排他性时钟是指在不同源点上定义但共享部分的时钟由于多路复用器或其他组合逻辑&#xff0c;它们的时钟树。时间限制向导识别此类时钟&#xff0c;并建议在它们这样做时直接对其进行时钟组约束除了连接到其共享时钟的逻辑之外&#xff0c;彼此之间…

半导体:Gem/Secs基本协议库的开发(5)

此篇是1-4 《半导体》的会和处啦&#xff0c;我们有了协议库&#xff0c;也有了通讯库&#xff0c;这不得快乐的玩一把~ 一、先创建一个从站&#xff0c;也就是我们的Equipment端 QT - guiCONFIG c11 console CONFIG - app_bundle CONFIG no_debug_release # 不会生…