什么是Bean的循环依赖?解决方案是什么?

Spring Bean循环依赖以及解决方案:

什么是Bean的循环依赖?

在这里插入图片描述

在这里插入图片描述

A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。

如图所示,Father类中有Son属性的成员变量,Son类中有Father属性的成员变量。这就是循环依赖。

public class Son {private String name;private Father father;}
public class Father {private String name;private Son son;
}

singleton下的set注入产生的循环依赖

导入坐标:

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.25</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.25</version></dependency>
</dependencies>

pojo:

Father实体类:

package com.stringzhua.pojo;/*** @Author Stringzhua* @Date 2024/9/14 16:07* description:*/
public class Father {private String name;private Son son;public String getName() {return name;}public void setName(String name) {this.name = name;}public Son getSon() {return son;}public void setSon(Son son) {this.son = son;}@Overridepublic String toString() {return "Father{" +"name='" + name + '\'' +", son=" + son.getName() +'}';}
}

Son实体类:

package com.stringzhua.pojo;/*** @Author Stringzhua* @Date 2024/9/14 16:07* description:*/
public class Son {private String name;private Father father;public String getName() {return name;}public void setName(String name) {this.name = name;}public Father getFather() {return father;}public void setFather(Father father) {this.father = father;}// toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。@Overridepublic String toString() {return "Son{" +"name='" + name + '\'' +", father=" + father.getName() +'}';}
}

xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="father" class="com.stringzhua.pojo.Father" scope="singleton"><property name="name" value="小头爸爸"></property><property name="son" ref="son"></property></bean><bean id="son" class="com.stringzhua.pojo.Son" scope="singleton"><property name="name" value="大头儿子"></property><property name="father" ref="father"></property></bean>
</beans>

测试类:

 @Testpublic void test01() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");Father father = (Father) applicationContext.getBean("father");Son son = (Son) applicationContext.getBean("son");System.out.println(father);System.out.println(son);}

测试结果:
在这里插入图片描述

通过测试得知:在singleton + set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。

prototype下的set注入产生的循环依赖

问:prototype+set注入的方式下,循环依赖会不会出现问题?

将xml的scope属性修改为prototype

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="father" class="com.stringzhua.pojo.Father" scope="prototype"><property name="name" value="小头爸爸"></property><property name="son" ref="son"></property></bean><bean id="son" class="com.stringzhua.pojo.Son" scope="prototype"><property name="name" value="大头儿子"></property><property name="father" ref="father"></property></bean>
</beans>

报错内容为:创建名为 ‘father’ 的 bean 时出错:请求的 bean 当前正在创建中:是否存在无法解析的循环引用?
在这里插入图片描述

通过测试得知,当循环依赖的所有Bean的scope="prototype"的时候,产生的循环依赖,Spring是无法解决的,会出现BeanCurrentlyInCreationException异常

以上两个Bean,如果其中一个是singleton,另一个是prototype,是没有问题的。为什么两个Bean都是prototype时会出错呢?
在这里插入图片描述

这里的循环依赖问题:
在这里插入图片描述

通过测试得知:在property + set注入的情况下,循环依赖Spring是无法解决的,会抛出BeanCurrentlyInCreationException异常

singleton下的构造注入产生的循环依赖

测试一下singleton + 构造注入的方式下,spring是否能够解决这种循环依赖。

public class Father {private String name;private Son son;public Father(String name, Son son) {this.name = name;this.son = son;}public String getName() {return name;}@Overridepublic String toString() {return "Father{" +"name='" + name + '\'' +", son=" + son +'}';}
}
public class Son {private String name;private Father father;public Son(String name, Father father) {this.name = name;this.father = father;}public String getName() {return name;}@Overridepublic String toString() {return "Son{" +"name='" + name + '\'' +", father=" + father +'}';}
}
	<bean id="father" class="com.stringzhua.pojo.Father" scope="singleton"><constructor-arg name="name" value="小头爸爸"></constructor-arg><constructor-arg name="son" ref="son"></constructor-arg></bean><bean id="son" class="com.stringzhua.pojo.Son" scope="singleton"><constructor-arg name="name" value="大头儿子"></constructor-arg><constructor-arg name="father" ref="father"></constructor-arg></bean>

测试:

 @Testpublic void test02() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");Father father = (Father) applicationContext.getBean("father");Son son = (Son) applicationContext.getBean("son");System.out.println(father);System.out.println(son);}

执行结果:发生了异常,信息如下:

在这里插入图片描述

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'father': Requested bean is currently in creation: Is there an unresolvable circular reference?at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)... 51 more

和上一个测试结果相同,都是提示产生了循环依赖,并且Spring是无法解决这种循环依赖的

主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的

Spring解决循环依赖的机理

Spring为什么可以解决set + singleton模式下循环依赖?

参考链接

根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。

实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。给Bean属性赋值的时候:调用setter方法来完成。

两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
在这里插入图片描述

在以上类中包含三个重要的属性:

Cache of singleton objects: bean name to bean instance. 单例对象的缓存:key存储bean名称,value存储Bean对象【一级缓存】Cache of early singleton objects: bean name to bean instance. 早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象【二级缓存】Cache of singleton factories: bean name to ObjectFactory. 单例工厂缓存:key存储bean名称,value存储该Bean对应的ObjectFactory对象【三级缓存】

这三个缓存其实本质上是三个Map集合。我们再来看,在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光。
在这里插入图片描述

从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。

总结:

Spring只能解决setter方法注入的单例bean之间的循环依赖。

ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。

11d6e7b6116b6ff2ccb3a37beb457352.png

Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。

面试常问到的:

1.关于三级缓存的问题,三级缓存的关键? 三级缓存能不能换成二级缓存?

三级缓存的关键是 实例化和初始化分开操作。

三级缓存的意义是存在代理,如果只用二级缓存的话(一个存放半成品,一个存放成品),如果要是有代理对象需要一层覆 盖掉原来的,所以又加了一层,形成三级(一个存放代理用于覆盖半成品,一个存放半成品,一个存放成品)。

在普通的循环依赖的情况下,三级缓存没有任何作用。三级缓存实际上跟Spring中的AOP相关。AOP场景下的getEarlyBeanReference 会拿到一个代理的对象,但是不确定有没有依赖,需不需要用到这个依赖对象,所以先给一个工厂放到三级缓存里。

这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象。

2.Spring中的循环引用是什么?

循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A

循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖

①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象

②二级缓存:缓存早期的bean对象(生命周期还没走完)

③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

3.如果产生了循环依赖,那解决流程清楚嘛?

第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories

第二,A在初始化的时候需要B对象,这个走B的创建的逻辑

第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories

第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键

第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects

第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects

第七,二级缓存中的临时对象A清除
4.Spring 如何解决循环依赖?

Spring bean注入流程
类实例化 -> 属性注入 -> 执行初始化方法 -> (如果有需要)生成代理对象 -> 使用

三级缓存解决循环依赖流程

A、B两个类相互依赖,初始化A的时候,第一步实例化A完成(生成对象工厂实例放入三级缓存),注入依赖属性B,一级缓存查询B没有,二级缓存查询B没有,

初始化B(生成对象工厂实例放入三级缓存),注入依赖属性A,一级缓存查询A没有,二级缓存查询A没有,三级缓存查询到A的对象工厂,需要AOP增强则生成A的代理对象,没有则直接创建A实例对象,并将A放入到二级缓存,注入A的代理对象完成,生成代理对象B,B移入一级缓存。

继续A属性注入(B的代理对象),然后可能还会依赖注入C对象流程和B一致,所有依赖注入完成后A初始化,生成A的代理对象,发现A的代理对象已存在,则跳过,放入一级缓存。此时A的代理对象也是提前生成的,但是仅针对循环依赖提前生成。
下面为流程图:
在这里插入图片描述

5.如果构造方法出现了循环依赖怎么解决?

由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

集群聊天服务器项目【C++】(六)MySql数据库

前面已经介绍了网络模块和业务模块&#xff0c;本章介绍数据模块&#xff0c;同样保持模块解耦的特性&#xff0c;即业务模块不能出现数据模块内容&#xff0c;如出现SQL语句&#xff0c;接下来看看怎么实现的。 1.环境安装 第一章已经介绍了MySql安装&#xff0c;但注意需要…

re题(24)BUUFCTF-[WUSTCTF2020]level1

BUUCTF在线评测 (buuoj.cn) 放到ida 这是下载的文本 逻辑比较简单&#xff0c;写个脚本 p[198,232,816,200,1536,300,6144,984,51200,570,92160,1200,565248,756,1474560,800,6291456,1782,65536000] for i in range(1,20):if (i & 1) ! 0 :p[i-1]chr(p[i-1] >> i)…

NVM(node.js版本工具)的使用

1.nvm是什么 NVM 是 Node Version Manager 的缩写&#xff0c;它是一个用于管理 Node.js 版本的命令行工具。通过NVM&#xff0c;你可以在同一台机器上安装和切换多个 Node.js 版本&#xff0c;对于开发和测试在不同 Node.js 版本上运行的应用程序非常有用。 2.下载 下载之前…

Linux:vim编辑技巧

命令模式 光标跳转 输入18&#xff0c;再输入G&#xff0c;可以跳转到18行。 复制、粘贴、删除 P是往上一行粘贴 小写u可以撤销 查找/撤销/保存 大写U可能失效&#xff0c;用CTRLr 末行模式 保存/退出/文件操作 字符串替换 开关参数的控制

AJAX 入门 day3

目录 1.XMLHttpRequest 1.1 XMLHttpRequest认识 1.2 用ajax发送请求 1.3 案例 1.4 XMLHttpRequest - 查询参数 1.5 XMLHttpRequest - 数据提交 2.Promise 2.1 Promise认识 2.2 Promise - 三种状态 2.3 案例 3.封装简易版 axios 3.1 封装_简易axios_获取省份列表 3…

FloodFill算法【下】

417. 太平洋大西洋水流问题 题目链接&#xff1a;417. 太平洋大西洋水流问题 题目解析 题目给我们一个矩阵&#xff0c;这个矩阵相当于陆地&#xff0c;被两个洋包围&#xff0c;左和上代表太平洋&#xff0c;右和下代表大西洋。 矩阵里面的数字代表海拔&#xff0c;水可以…

【磨皮美白】基于Matlab的人像磨皮美白处理算法,Matlab处理

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于Matlab的图像磨皮美白处理&#xff0c;用matlab实现。 一、案例背景和算法介绍 …

【C++】【网络】【Linux系统编程】单例模式,加锁封装TCP/IP协议套接字

目录 引言 获取套接字 绑定套接字 表明允许监听 单例模式设计 完整代码示例 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 引言 有关套接字编程的细节和更多的系统调用课参考《UNIX环境高级编程》一书&#xff0c;可以在如下网站搜索电子版&#xff0c;该书在第16章详…

杨氏矩阵中查找想要找到的数

文章目录 一、题目二、思路三、代码实现 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、题目 二、思路 第一步 根据杨氏矩阵的规则说明矩阵从左到右递增&#xff0c;从上往下递增&#xff0c;因此我们可以画出这样的图。 对于杨氏矩阵&#xff0…

FPGA与Matlab图像处理之直方图均衡化

文章目录 一、什么是直方图?二、什么是直方图均衡化&#xff1f;三、Matlab实现直方图均衡化的步骤第一步&#xff1a; 彩色图像转成灰度图像第二步&#xff1a;提取亮度通道的直方图第三步&#xff1a;累计亮度通道的像素值频率第四步&#xff1a; 映射到新的灰度值 四、Veri…

10.2高斯金字塔-向上取样

实验原理 在OpenCV中&#xff0c;高斯金字塔&#xff08;Gaussian Pyramid&#xff09;和上采样&#xff08;Upsampling&#xff09;是图像处理中的常见技术&#xff0c;它们经常用于图像的多分辨率分析。高斯金字塔主要用于图像的多尺度表示&#xff0c;而上采样则是将图像放…

Porcupine - 语音关键词唤醒引擎

文章目录 一、关于 Porcupine特点用例尝试一下 语言支持性能 二、Demo1、Python Demo2、iOS DemoBackgroundService DemoForegroundApp Demo 3、网页 Demo3.1 Vanilla JavaScript 和 HTML3.2 Vue Demos 三、SDK - Python 一、关于 Porcupine Porcupine 是一个高度准确和轻量级…

数据结构之线性表(python)

华子目录 线性表的定义前驱与后继 1.顺序表&#xff08;顺序存储结构&#xff09;python列表与数组的区别列表数组 1.1插入数据实例 1.2删除元素实例 1.3查找元素1.4修改元素1.5综合示例 2.单链表2.1单链表的初始化2.2插入元素示例注意 2.3删除元素示例 2.4修改元素2.5查找元素…

图解Self-Attention和代码实现,大语言模型基础思维导图

文章目录 1 Self-Attention的概念注意优缺点 2 Self-Attention的原理Q,K,V, and Self-Attention计算公式代码实现 Self-Attention的计算细节输入是如何Embedding的&#xff1f;Word EmbeddingsSentence EmbeddingsPre-trained Embeddings SelfAttention是如何计算的计算图 4 Se…

无畏契约 (Valorant)YOLO 模型数据集

4万数据集 无畏契约 Valorant YOLO 模型 数据集 截图大小&#xff1a;256x256 截图数量&#xff1a;40000包含保安拌线&#xff0c;被闪被黑&#xff0c;蝰蛇大招内 模型类别&#xff1a;2类 头身类 1身0头 人物&#xff1a;黄色色盲 已添加部分负样本&#xff0c;防止识别除敌…

数据结构——“二叉搜索树”

二叉搜索树是一个很重要的数据结构&#xff0c;它的特殊结构可以在很短的时间复杂度找到我们想要的数据。最坏情况下的时间复杂度是O(n)&#xff0c;最好是O(logn)。接下来看一看它的接口函数的实现。 为了使用方便&#xff0c;这里采用模版的方式&#xff1a; 一、节点 temp…

docker部署Stirling-PDF

github网址&#xff1a; GitHub - Stirling-Tools/Stirling-PDF: #1 Locally hosted web application that allows you to perform various operations on PDF files 1、官方docker镜像无法拉取&#xff0c;使用别人阿里云私人镜像仓库下载Stirling-PDF镜像&#xff1a; regi…

微服务以及注册中心

一、什么是微服务 微服务是指开发一个单个小型的但有业务功能的服务&#xff0c;每个服务都有自己的处理和轻量通讯机制&#xff0c;可以部署在单个或多个服务器上。微服务也指一种松耦合的、有一定的有界上下文的面向服务架构。也就是说&#xff0c;如果每个服务都要同时修改…

Objects as Points基于中心点的目标检测方法CenterNet—CVPR2019

Anchor Free目标检测算法—CenterNet Objects as Points论文解析 Anchor Free和Anchor Base方法的区别在于是否在检测的过程中生成大量的先验框。CenterNet直接预测物体的中心点的位置坐标。 CenterNet本质上类似于一种关键点的识别。识别的是物体的中心点位置。 有了中心点之…

AI助力遥感影像智能分析计算,基于高精度YOLOv5全系列参数【n/s/m/l/x】模型开发构建卫星遥感拍摄场景下地面建筑物智能化分割检测识别系统

随着科技的飞速发展&#xff0c;卫星遥感技术已成为获取地球表面信息的重要手段之一。卫星遥感图像以其覆盖范围广、数据量大、信息丰富等特点&#xff0c;在环境监测、城市规划、灾害评估等多个领域发挥着不可替代的作用。然而&#xff0c;面对海量的卫星图像数据&#xff0c;…