Spring实例化源码解析之循环依赖CircularReference(十三)

news2024/12/29 9:05:45

前言

首先什么是循环依赖,简单说就是互相引用。在Spring中是默认支持循环依赖的,至于怎么解决的循环依赖问题,就是本章要探讨的内容。

// 默认允许循环依赖
private boolean allowCircularReferences = true;

//提供set方法
public void setAllowCircularReferences(boolean allowCircularReferences) {
    this.allowCircularReferences = allowCircularReferences;
}

准备

需要创建两个类,他们需要和下图所示一样的进行循环依赖。

在这里插入图片描述

创建两个最简单的TestA对象和TestB对象,代码如下:

@Service
public class TestA {

	@Autowired
	TestB testB;
}

@Service
public class TestB {
	@Autowired
	TestA testA;
}

TestA和TestB的实例化流程

Bean的实例化这章讲述了一般bean的实例化流程,所以直接在DefaultListableBeanFactory的preInstantiateSingletons方法开始,打一个条件断点来逐步分析。

在这里插入图片描述

我们创建的TestA是单例对象,并且不是FactoryBean,也无需lazyInit,所以下一步我们直接去doGetBean。

doGetBean(testA)

到达doGetBean方法,会去从一级缓存中获取,此时肯定是没有的,所以直接返回null。继续执行后续else中的逻辑。

在这里插入图片描述

getSingleton(testA,testAObjectFactory)

else逻辑中会进入到getSingleton这个带有函数式接口的方法。在这里我将传入的函数式接口命名为testAObjectFactory,为了方便描述,实际代码如下

sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});

getSingleton这个方法会有一个小操作beforeSingletonCreation(beanName),就是将当前准备创建的testA放入singletonsCurrentlyInCreation,也就是标记这个对象正在创建中。

在这里插入图片描述

标记完testA对象之后调用testAObjectFactory,就是doCreateBean的逻辑了。

doCreateBean(testA)

在做创建Bean的逻辑时,有一个属性earlySingletonExposure,翻译过来就是早期的单例暴露。而是否需要早期暴露单例对象是通过allowCircularReferences、singletonsCurrentlyInCreation和testA是否是单例来判断的。所以这也就能知道为什么默认允许循环引用和为什么要先标记对象正在创建中了。

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

而这里的早期暴露就是大家所知道的提前曝光,核心就是addSingletonFactory。会把传入的函数式接口存在二级缓存中,如下图所示:

在这里插入图片描述

populateBean(testA)

然后使用AutowiredAnnotationBeanPostProcessor的postProcessProperties方法来实际进行属性值的注入

在这里插入图片描述

获取到Field之后通过beanFactory.getBean(testB)来继续创建TestB来满足TestA的实例化。

在这里插入图片描述

doGetBean(testB)

testB当前也没有实例化过,所以这边从一级缓存中也是获取不到的,直接返回null进入else逻辑

在这里插入图片描述

getSingleton(testB,testBObjectFactory)

getSingleton方法里会把testB标记为正在创建中。然后执行testBObjectFactory函数式接口,也就是createBean(testB)

在这里插入图片描述

在这里插入图片描述

doCreateBean(testB)

同样testB也满足提前曝光的要求,所以会把testB也放入到三级缓存中

在这里插入图片描述

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

populateBean(testB)

然后继续进行属性输入,因为testB依赖了TestA,所以会执行beanFactory.getBean(testA),所以直接进入doGetBean方法。

doGetBean(testA)

进度doGetBean方法之后就是直接getSingleton(testA)

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		String beanName = transformedBeanName(name);
		Object beanInstance;

		// Eagerly check singleton cache for manually registered singletons.
		// !!!!!!!!!!!!!!!!!三级缓存的核心
		Object sharedInstance = getSingleton(beanName);

getSingleton(testA)

因为testA对象在前面将其标记为正在创建中了,并且一级缓存当前还没有testA实例对象,因为testA这个时候还没有实例化完成。所以三级缓存中存在testA的ObjectFactory对象。

在这里插入图片描述

回忆一下我们的提前曝光放入的内容

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				// 获取早期bean得引用
          				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

所以在singletonFactory调用getObject方法的时候会执行这个getEarlyBeanReference方法来获取对象,将获取到的对象放入二级缓存,并将三级缓存情况,此时三级缓存就只有testB了。getEarlyBeanReference这个方法是用来判断对象是否需要被代理,如果不需要会返回原始bean。

在这里插入图片描述

initializeBean(testB)

在这里插入图片描述

getSingleton(testB,testBObjectFactory)

执行完testObjectFactory函数式接口之后,DefaultSingletonBeanRegistry中的getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法会继续执行。

在这里会把testB放入到一级缓存中,并且移除二三级缓存。

protected void addSingleton(String beanName, Object singletonObject) {
		// 线程安全的
		synchronized (this.singletonObjects) {
			// 放入一级缓存
			this.singletonObjects.put(beanName, singletonObject);
			// 移除三级缓存
			this.singletonFactories.remove(beanName);
			// 移除二级缓存
			this.earlySingletonObjects.remove(beanName);
			// 已注册的map中把新初始化的bean放入
			this.registeredSingletons.add(beanName);
		}
	}

initializeBean(testA)

在这里插入图片描述

getSingleton(testA,testAObjectFactory)

同样执行完initializeBean(testA)之后会把testA放入到一级缓存

怎么解决的?

首先我们经过上述一步一步的断点方式,了解整体的运行方式,这边使用一张图来进行概括。

在这里插入图片描述

从图中可以看出提前曝光是解决循环依赖的核心。而allowCircularReferences、singletonsCurrentlyInCreation就是辅助其工作,让其在循环依赖的情况下能正常运作。

为什么是三级缓存?

很遗憾,我也曾被问到过这个三级缓存的面试题,面试官问过我为什么是三级而不是二级。这个问题其实就是问一下你对spring三级缓存的个人理解。

以下是个人见解,仅供参考:

一级缓存singletonObjects,这个毫无疑问得要。所以如果是二级缓存的话,我们必须在二级缓存和三级缓存中做取舍。

假设二级缓存没有,也就是earlySingletonObjects这个缓存不存在。当发生循环依赖的时候,会出现对象不一致的问题。举例如下:

假设A中注入B和C,B中注入A,C中也注入A。

如果没有二级缓存,B中和C中的A可能不是同一个。这种情况就会出现不一致的问题,所以earlySingletonObjects得要。

假设三级缓存没有,也就是说提前曝光的时候直接执行完函数式接口返回对象放入二级缓存中。个人感觉是没有任何问题的。

综上所述,可以二级,也就是singletonObjects和earlySingletonObjects得存在。

那么既然两级可以解决,为什么spring使用得三级缓存,个人认为三级是可以提高效率的。并不是所有的bean都存在循环依赖,也就是说提前曝光的ObjectFactory也不是都需要执行,这么做可能(猜测)是为了提高效率。

说到最后

当两个或多个Bean之间的构造函数形成循环依赖时,Spring无法确定哪个Bean应该先创建,因为每个Bean的创建都依赖于其他Bean。这种情况下,Spring无法通过构造函数注入解决循环依赖,从而导致异常的抛出。

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘testB’ defined in file [G:\qhyu-spring\spring-framework\spring-qhyu\build\classes\java\main\com\qhyu\cloud\circlarRefrence\TestB.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘testA’: Requested bean is currently in creation: Is there an unresolvable circular reference?

@Service
public class TestA {

	public TestA(TestB testB) {
		this.testB = testB;
	}

	TestB testB;
}
@Service
public class TestB {
	public TestB(TestA testA) {
		this.testA = testA;
	}

	TestA testA;
}

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

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

相关文章

Jmeter压测工具和Docker服务端接口压测的安装使用详细教程

安装 参考资料 版本 切换语言 使用步骤 1、新建线程组&#xff1b; 右键“测试计划”(左侧导航中的第一条) > 添加 > 线程&#xff08;用户&#xff09; > 线程组&#xff1b; 线程数&#xff1a;并发数&#xff1b; 2、添加取样器 > HTTP请求&#xff1b; 右…

菜尼奥排错之AttributeError: module ‘mmcv‘ has no attribute ‘dump‘

环境版本&#xff1a; 阿里云PAI平台创建的实例&#xff0c;linux x86-64基础配置如下&#xff1a; pytorch 1.12.0 mmengine 0.8.4 mmcv 2.0.1 mmdet 3.1.0 mmdet3d 1.2.0 报错信息&#xff1a; AttributeError: module mmcv has no attribute dump 本人是在mmcv.dum…

第18章 SpringCloud生态(三)

18.21 Nacos能存储什么样格式的数据(配置中心) 难度:★ 重点:★ 白话解析 看下面这副Nacos控制台的截图就明白了 参考答案 六种格式数据:Text、JSON、XML、Yaml、HTML和Properties格式。 18.22 Nacos是如何实现配置动态更新的(配置中心) 难度:★★ 重点:★★★ 白话…

Linux命令之网络命令ifconfig

一、ifconfig命令简介 ifconfig命令是Linux系统下的一个网络配置工具&#xff0c;用于查看和设置网络接口的配置信息。通过ifconfig命令&#xff0c;用户可以查看当前系统中所有网络接口的详细信息&#xff0c;如IP地址、子网掩码、广播地址等。同时&#xff0c;用户还可以使用…

vue判断是pc端还是手机端访问

首先编写一个util.ts工具类&#xff0c;用作判断访问进来的是pc还是手机端 //判断是否是移动端 export function _isMobile(): boolean {return navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|w…

2023年10月24日程序员节

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

源码编译安装Apache

目录 ✨apache安装步骤 &#x1f36d;挂载镜像 &#x1f36d;解压并安装 &#x1f36d;安装主程序 &#x1f36d;优化链接及服务 &#x1f36d;查看httpd模块 &#x1f36d;查看mpm配置文件 &#x1f36d;查看apache主页 &#x1f36d;使用ab命令进行压力测试 &#x1f990;博客…

【idea】win 10 / win 11:idea 、Alibaba Dragonwell 11、maven、git下载与安装

目录 一、安装 jdk&#xff1a;Alibaba Dragonwell 11 &#xff08;1&#xff09;下载&#xff1a;Alibaba Dragonwell 11 &#xff08;2&#xff09;解压&#xff1a;Alibaba Dragonwell 11 &#xff08;3&#xff09;配置系统环境变量&#xff1a;jdk &#xff08;4&a…

二、虚拟机克隆和快照

1.虚拟机克隆 如果你已经安装了一台Linux操作系统&#xff0c;你还想要更多的&#xff0c;这个时候没有必要重新装&#xff0c;只需要克隆就可以 方式1&#xff1a;直接拷贝一份安装好的虚拟机文件 方式2&#xff1a;使用vmware的克隆操作 注意&#xff0c;克隆时&#xff0c;…

“深入理解C++类默认成员函数:探索构造、析构与复制“

文章目录 类的8个默认成员函数构造函数析构函数拷贝构造移动构造赋值运算符重载移动赋值运算符重载取地址及const取地址操作符重载 强制生成默认函数的关键字default禁止生成默认函数的关键字delete 类的8个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空…

[黑马程序员SpringBoot2]——基础篇1

目录&#xff1a; SpringBoot入门案例&#xff08;Idea联网版&#xff09;SpringBoot入门案例&#xff08;官网创建版&#xff09;SpringBoot入门案例&#xff08;阿里云版&#xff09;SpringBoot入门案例&#xff08;手工制作版&#xff09;教你一招&#xff0c;隐藏文件或文件…

【大数据】Hadoop

文章目录 概述Hadoop组成HDFSMapReduce写MapReduce程序&#xff08;Hadoop streaming&#xff09; YARNHadoop 启动 工作方式Hadoop的主从工作方式Hadoop的守护进程 运行模式本地运行模式伪分布式运行模式完全分布式运行模式 Hadoop高可用的解决方案ZooKeeper quorumZKFC 环境搭…

k8s集群镜像下载加gradana监控加elk日志收集加devops加秒杀项目

展示 1.配套资料2.devops 3.elk日志收集 4.grafana监控 5.dashboard![在这里插入图片描述](https://img-blog.csdnimg.cn/bf294f9fd98e4c038858a6bf5c34dbdc.png 目的 学习k8s来来回回折腾很久了&#xff0c;光搭个环境就能折腾几天。这次工作需要终于静下心来好好学习了一…

WebAPI项目在Linux服务器上部署记录

对已有的WebAPI项目进行发布 发布流程 需要把publish的文件夹直接上传至linux服务器 在Linux服务器上部署环境 检查是否安装了dotnet环境 直接命令行输入 dontnet&#xff0c;如果弹出的是下面的语句&#xff0c;说明没有安装dotnet环境 -bash: dotnet:command not found…

软件开发工具总结篇

作为软件开发人员&#xff0c;我们需要使用许多工具来帮助我们完成工作。这些工具包括编程语言、编辑器、集成开发环境&#xff08;IDE&#xff09;和其他辅助工具。在本文中&#xff0c;我们将讨论一些常见的软件开发工具&#xff0c;并对它们进行比较。 编程语言 编程语言是…

SpringCloud复习:(1)netflix包里的DiscoveryClient类

DiscoveryClient类实现了EurekaClient接口 它的主要作用&#xff1a;服务注册&#xff0c;服务续约&#xff0c;服务下线&#xff0c;获取服务列表。 initScheduledTasks方法用来开启定时任务来完成上述功能。 上图中的代码用来从服务器定期&#xff08;默认30秒&#xff09;…

TLSR825x之BDT烧录固件

泰凌烧录调试工具&#xff08;BDT&#xff09;适用于泰凌全系列。在SDK开发过程中&#xff0c;BDT的功能包括“擦除Flash扇区”、“下载固件”、“通信失败时激活MCU”、“访问包括FLASH /CORE /ANALOG /OTP在内的存储空间”、“读/写全局变量”和“查看USB 日志”。 泰凌BDT_…

解密一致性哈希算法:实现高可用和负载均衡的秘诀

解密一致性哈希算法&#xff1a;实现高可用和负载均衡的秘诀 前言第一&#xff1a;分布式系统中的数据分布问题&#xff0c;为什么需要一致性哈希算法第二&#xff1a;一致性hash算法的原理第三&#xff1a;一致性哈希算法的优点和局限性第四&#xff1a;一致性哈希算法的安全性…

初识JAVA,带你入门

本章重点&#xff1a; 1. Java语言简介、发展概述、语言优势、与C/C区别 2. 初识Java程序入口之main方法 3. 注释、标识符、关键字 1. Java语言概述 1.1 Java是什么&#xff1f; Java是一种优秀的程序设计语言&#xff0c;它具有令人赏心悦目的语法和易于理解的语义…

结构体、枚举、位段、联合体详解

&#x1f388;个人主页&#xff1a;.满船清梦压星河_-CSDN博客 &#x1f302;c/c领域新星创作者 &#x1f389;欢迎&#x1f44d;点赞✍评论❤️收藏 &#x1f61b;&#x1f61b;&#x1f61b;希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xf…