来聊聊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,没有就递归调用一下再set
			field.set(obj, singletonObjects.containsKey(filedName) ? singletonObjects.get(filedName) : getBean(filedCLass));

		}
		//最终再返回这个bean
		return  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 {
	@Autowired
	private BService bService;

	public BService getbService() {
		return bService;
	}

	public void setbService(BService bService) {
		this.bService = bService;
	}
}

@Service("bService")
public class BService {

	@Autowired
	private 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解决了循环依赖问题

在这里插入图片描述

总结成流程图

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023年底总结丨5大好用的局域网监控软件

不知不觉间2023年又到结尾了&#xff0c;今年我们服务过很多想要电脑监控软件的客服&#xff0c;也服务了很多想要加密软件的客户。 这一年&#xff0c;我们走得不疾不徐&#xff0c;走得稳而坚定&#xff1b;这一年&#xff0c;我们累积服务超过万计客户&#xff1b;这一年&a…

面试官:这些大学生都会

大家好&#xff0c;我是 JavaPub。 最近有些同学在后台问我&#xff0c;面试总是会遇到被问 Linux 命令的问题&#xff0c;自己就面试个后端开发岗位&#xff0c;怎么这么难呢&#xff1f; 其实 Linux 命令&#xff0c;对于一个后端开发来说&#xff0c;并不是很难&#xff0c…

springcloudalibaba01

整合springcloud 和 springcloudalibaba&#xff0c;&#xff0c;&#xff0c; 版本对应关系 <dependencyManagement><dependencies><!--每个springcloud的工具都有一个版本每个springcloud alibaba的工具都有一个版本统一版本--> <!-- 整合…

spring boot 实现直播聊天室

spring boot 实现直播聊天室 技术方案: spring bootwebsocketrabbitmq 使用 rabbitmq 提高系统吞吐量 引入依赖 <dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.42&…

广州华锐互动:汽车电子线束加工VR仿真培训与实际生产场景相结合,提高培训效果

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经逐渐渗透到各个领域&#xff0c;为企业和个人带来了前所未有的便利。在汽车制造行业中&#xff0c;线束加工作为一项关键的生产工艺&#xff0c;其质量直接影响到汽车的性能和安全。因此&#xff0c;…

【UE】在蓝图中修改材质实例的参数的两种方式

目录 方式一、通过“在材质上设置标量/向量参数值”节点实现 方式二、通过“设置标量/向量参数值”节点实现 方式一、通过“在材质上设置标量/向量参数值”节点实现 1. 在材质中设置了两个参数 2. 创建材质实例 3. 创建一个蓝图&#xff0c;对静态网格体赋予材质实例 在事件…

基于虚拟机下的win7系统安装简记

文章目录 安装系统激活win7提示系统保留分区未分配驱动器问题使用win7 Active激活系统根据dns分配的ip地址将网络改为固定ip&#xff0c;然后关闭防火墙&#xff0c;即可完成虚拟机与宿主机互通 安装系统 在虚拟机中找到自己下载win7镜像文件&#xff0c;配置完成后一路next即…

MySQL数据库卸载-Windows

目录 1. 停止MySQL服务 2. 卸载MySQL相关组件 3. 删除MySQL安装目录 4. 删除MySQL数据目录 5. 再次打开服务&#xff0c;查看是否有MySQL卸载残留 1. 停止MySQL服务 winR 打开运行&#xff0c;输入 services.msc 点击 "确定" 调出系统服务。 2. 卸载MySQL相关组…

虚幻学习笔记13—C++静态和动态加载

一、前言 我们在蓝图中可以很方便的添加各种需要的组件&#xff0c;那么在C代码中要如何实现呢。在代码中分静态和动态加载&#xff0c;而无论静态和动态&#xff0c;加载的内容有资源和资源类&#xff0c;资源类通常为带资源的蓝图类。 二、实现 在实现静态或动态加载时&…

什么是回调函数

需求 A&#xff0c;B两个小组开发一个功能。B小组开发制作油条模块:make_youtiao。A小组需要调用B小组开发的模块&#xff0c;然后执行后续的操作&#xff1a;sell()如下图&#xff1a; 上面的方式A小组必须等待B小组开发的模块make_youtiao执行完成后才能执行sell()。 上图代…

力扣 | 98. 验证二叉搜索树

98. 验证二叉搜索树 中序遍历 (边遍历边验证顺序性) private TreeNode prev null;private boolean isBST true;public boolean isValidBST(TreeNode root) {inorder(root);return isBST;}private void inorder(TreeNode node) {if (node null) return;inorder(node.left);…

什么是第一方数据,如何使用它?

多年来&#xff0c;第一方数据一直是营销行业的话题。 随着用户数据隐私法律法规的不断收紧&#xff0c;营销人员必须接受一个几乎没有数据 cookie 的世界。 我们必须在如何合法和合乎道德地获取客户信息方面更具创造性。 不确定什么是第一方数据&#xff1f;或者不太确定从…

关于嵌入式开发的一些信息汇总:C标准、芯片架构、编译器、MISRA-C

关于嵌入式开发的一些信息汇总&#xff1a;C标准、芯片架构、编译器、MISRA-C 关于C标准芯片架构是什么&#xff1f;架构对芯片有什么作用&#xff1f;arm架构X86架构mips架构小结 编译器LLVM是什么&#xff1f;前端在干什么&#xff1f;后端在干什么&#xff1f; MISRA C的诞生…

基于JSP+Servlet+Mysql的建设工程监管信息

基于JSPServletMysql的建设工程监管信息 一、系统介绍二、功能展示1.企业信息列表2.录入项目信息3.项目信息列表 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目名称&#xff1a;基于JSPServlet的建设工程监管信息 项目架构&#xff1a;B/S架构 开发语言&#xff1a;…

持续集成交付CICD:Jenkins使用CD流水线下载Nexus制品

目录 一、实验 1.Jenkins使用CD流水线下载Nexus制品 一、实验 1.Jenkins使用CD流水线下载Nexus制品 &#xff08;1&#xff09;Jenkins新建CD流水线 &#xff08;2&#xff09;新建视图 &#xff08;3&#xff09;查看视图 &#xff08;4&#xff09;添加字符参数 &#xf…

「Verilog学习笔记」 Johnson Counter

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 timescale 1ns/1nsmodule JC_counter(input clk ,input rst_n,output reg [3:0] Q );always (posedge clk or negedge rst_n) begin…

Github详细使用教程

1. 什么是 Github? github是一个基于git的代码托管平台&#xff0c;付费用户可以建私人仓库&#xff0c;我们一般的免费用户只能使用公共仓库&#xff0c;也就是代码要公开。 Github 由Chris Wanstrath, PJ Hyett 与Tom Preston-Werner三位开发者在2008年4月创办。迄今拥有5…

Mybatis-plus介绍与入门

前言 MyBatis-Plus是在MyBatis基础上的一个增强工具库&#xff0c;旨在简化开发者的工作&#xff0c;提高开发效率&#xff0c;同时保留MyBatis的灵活性。使用 MyBatis-Plus 可以减少重复性的代码&#xff0c;简化常见的数据库操作 官方学习文档&#xff1a;MyBatis-Plus (bao…

深度学习 Day16——P5运动鞋识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 文章目录 前言1 我的环境2 代码实现与执行结果2.1 前期准备2.1.1 引入库2.1.2 设置GPU&#xff08;如果设备上支持GPU就使用GPU,否则使用C…

【k8s】使用Finalizers控制k8s资源删除

文章目录 词汇表基本删除操作Finalizers是什么&#xff1f;Owner References又是什么&#xff1f;强制删除命名空间参考 你有没有在使用k8s过程中遇到过这种情况: 通过kubectl delete指令删除一些资源时&#xff0c;一直处于Terminating状态。 这是为什么呢&#xff1f; 本文将…