Spring 三级缓存解决循环依赖源码分析

news2024/12/25 12:24:25

什么是循环依赖?

ServiceA依赖ServiceB,ServiceB依赖ServiceA。
启动Spring项目时,如果想实例化ServiceA,创建完ServiceA对象后,需要依赖注入ServiceB的对象,而ServiceB实例化时,需要ServiceA,如此反复,导致循环依赖。

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}
@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

在这里插入图片描述
#Spring如何解决循环依赖问题?
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry中有三个Map缓存了单例对象的信息

	/** Cache of singleton objects: bean name to bean instance. */
	//缓存的是已经创建完成,且完成依赖注入的单例对象(一级缓存)
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
	//缓存的是对象工厂(三级缓存)
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	//缓存的是已经创建完成,但未完成依赖注入的单例对象(二级缓存)
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
  • 一级缓存:缓存的是已经创建完成,且完成依赖注入的单例对象
  • 二级缓存:缓存的是已经创建完成,但未完成依赖注入的单例对象
  • 三级缓存:缓存的是对象工厂

大概步骤:

  1. 从缓存中获取ServiceA的对象实例,如果有则返回(第一次肯定没有),如果没有,则创建ServiceA对象实例。
  2. 创建ServiceA对象并放入缓存中、依赖注入ServiceB对象。
  3. 创建ServiceB对象实例并放入缓存中,依赖注入ServiceA对象。
  4. 从缓存中获取ServiceA对象,依赖注入到ServiceB中,ServiceB对象创建成功。
  5. 回到步骤2,ServiceA对象创建成功。

源码分析

随便建立个SpringBoot项目,在AbstractBeanFactory的Object getBean(String name) throws BeansException方法上打断点,因为主要观察ServiceA、ServiceB的实例化,因此打上条件断点name.equals(“serviceA”)||name.equals(“serviceB”),调试启动项目。
在这里插入图片描述
这就是调用链,可以看到代码是如何走到getBean方法的。
在这里插入图片描述
AbstractBeanFactory.doGetBean(
String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException
从一二三级缓存中获取ServiceA单例实例
在这里插入图片描述
在这里插入图片描述
第一次肯定获取不到。
如果是单例,从一级缓存中获取ServiceA的实例对象,取不到则调用createBean方法创建ServiceA实例
在这里插入图片描述

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);//从一级缓存中获实例
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					singletonObject = singletonFactory.getObject();//会回调上面的createBean方法
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);//将创建的单例对象放入到一级缓存,同时移除二级三级缓存
				}
			}
			return singletonObject;
		}
	}

createBean方法如下
在这里插入图片描述
doCreateBean方法如下

  • 首先实例化对象

  • 如果一级缓存中没有该对象,会将实例的对象放入到三级缓存中,同时移除二级缓存。

  • 然后依赖注入。这里ServiceA依赖ServiceB,因此又回到了AbstractBeanFactory.getBean方法来获取ServiceB。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		//返回的是一个BeanWrapper对象
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
		//创建bean对象
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		// Allow post-processors to modify the merged bean definition.
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		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));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			//依赖注入
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

源码分析总结

上面代码的链路已经了解,做下总结。

1.1 首先获取ServiceA的实例AbstractBeanFactory.getBean(String name),name是"serviceA"
1.2 AbstractBeanFactory.doGetBean。
1.2.1 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton,从三级缓存中获取serviceA实例,未获取到。如果取到了直接返回。
1.2.2 如果是原型,直接调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean方法,创建实例。
如果是单例,调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton方法获取单例
1.3 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton
1.3.1 从一级缓存中获取serviceA,未获取到。取到了则直接返回
1.3.2 未取到serviceA则通过singletonObject = singletonFactory.getObject()回调org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean方法创建实例
1.3.3 调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry方法,如果一级缓存中没有实例,则将实例工厂放入到三级缓存中,并将实例从二级缓存中移除
1.4 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean
1.4.1 调用doCreateBean方法
1.5 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean
1.5.1 调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance方法创建实例,并包装成BeanWrapper。
1.5.2 调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean进行依赖注入,依赖注入时会获取serviceB实例,回到第一步。
1.5.3 调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.addSingletonFactory

流程图

如果A依赖B,且B依赖A。

  • 首先getBean获取A实例,第一次创建缓存中都没有,会走到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean方法创建Bean,创建完A实例对象,并且将A对象的工厂放入到三级缓存后,执行依赖注入B。
  • 依赖注入B时需要获取B对象,缓存中没有,依然会走到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean方法创建Bean,创建完B实例对象,并且将B对象的工厂放入到三级缓存后,执行依赖注入A。
  • 这一步是解决循环依赖的重点,依赖注入A时需要获取A对象。三级缓存中有A对象工厂,根据工厂生成A对象并放入到二级缓存中(此时的A对象没有依赖注入B对象,A中存的B时null),且将一级三级缓存中的A对象移除。这里涉及到AOP,如果对象需要AOP,则根据工厂生成AOP的代理对象,这里不展开了。
  • 依赖注入A对象成功后,B对象实例化成功,并将B对象缓存到一级缓存中,并将二三级缓存中的B对象移除。
  • 回到A依赖注入B的步骤,将B注入A中,A对象实例化成功,并将A对象缓存到一级缓存中,并将二三级缓存中的A对象移除。
  • A、B实例化完成后,只有一级缓存中存有完整的A、B对象,二三级缓存中没有A、B对象
    在这里插入图片描述

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

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

相关文章

大模型场景应用全集:持续更新中

一、应用场景 1.办公场景 智能办公&#xff1a;文案生成&#xff08;协助构建大纲优化表达内容生成&#xff09;、PPT美化&#xff08;自动排版演讲备注生成PPT&#xff09;、数据分析&#xff08;生成公式数据处理表格生成&#xff09;。 智能会议&#xff1a;会议策划&…

C++之 bind 绑定器深入学习:从入门到精通!

简介 本文详细阐述了 C 中关于 bind 绑定器技术的基本概念和常用技巧。 引入动机 在标准算法库中&#xff0c;有一个算法叫 remove_if&#xff0c;其基本使用如下&#xff1a; #include <iostream> #include <string> #include <algorithm> #include &l…

FANUC发那科模块 A03B-0823-C003 I/0 EXT

IO模块接线 在FANUC系统中IO模块的种类比较多&#xff0c;每种IO模块的使用场合也不相同&#xff0c;每种IO模块的接线脚位也有很大区别&#xff0c;对于电气设计人员来说&#xff0c;清楚知道常用IO模块的接线脚位&#xff0c;才能更好的规划地址、设计图纸&#xff0c;对于设…

MySQL多表

表关系 1.一对多 应用场景 班级和学生 部门和员工 建表原则 设置&#xff08;ForeginKey&#xff09;外键连接 一个表的外键即为另外一张表的主键,以此简历两张表的关系 因此需要再学生表中新增一列&#xff0c;命名为 班级表_id&#xff0c;即班级表的主键&#xff0c;又叫…

【力扣】572.另一棵树的子树

题目描述 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看…

电脑屏幕录制工具分享5款,附上详细电脑录屏教程(2024全新)

日月更迭&#xff0c;转眼间已经来到了2024年的立秋&#xff0c;在这个数字技术快速发展的时代&#xff0c;电脑录屏技术已经成为了一项不可或缺的技能&#xff0c;无论是用于工作汇报、在线教学、游戏直播还是个人娱乐。那么录屏软件哪个好用呢&#xff1f;接下来&#xff0c;…

QT按钮组

目录 按钮组 Push Button&#xff08;按钮&#xff09; Tool Button&#xff08;图片文字&#xff09; Radio Button(单选&#xff09; Check Button(多选) Command Link Button Dialog Button Box(对话按钮&#xff09; 按钮组 Push Button&#xff08;按钮&#xff09…

手机游戏录屏软件哪个好,3款软件搞定游戏录屏

在智能手机普及的今天&#xff0c;越来越多的人喜欢在手机上玩游戏&#xff0c;并希望能够录制游戏过程或者分享游戏技巧。然而&#xff0c;面对市面上众多的手机游戏录屏软件&#xff0c;很多人可能会陷入选择困难。究竟手机游戏录屏软件哪个好&#xff1f;在这篇文章中&#…

数据跨境传输的安全合规风险如何规避?获取免费解决方案白皮书

在全球化的背景下&#xff0c;企业进行有 效的资源整合&#xff0c;学习海外市场的先进技术和管理经验&#xff0c;寻找新的增长点&#xff0c;实现业务的多元化和 可持续发展&#xff0c;不仅有利于开辟新市场&#xff0c;更有助于巩固和增强企业在全球中的地位。在这种前景 下…

如何把项目上传到Gitee(超详细保姆级教程)

目录预览 一、远程仓库1、新建远程仓库1.1 克隆/下载信息介绍 2、新建分支3、配置私人令牌 二、本地仓库1、初始化本地仓库2、创建分支&#xff0c;并切换到该分支3、设置用户名、邮箱3.1 全局3.2 局部 4、设置Remote地址4.1 远程仓库有文件4.2 远程仓库没有文件 5、拉取最新代…

全面掌握Xilinx FPGA开发技术与实战技巧

FPGA以其灵活性、可定制性和并行处理能力&#xff0c;为工程师提供了实现创新解决方案的强大工具。对于初学者来说&#xff0c;学习FPGA开发需要掌握一些基础知识和技能。 学习FPGA必备的基础知识点&#xff1a; 数字逻辑基础&#xff1a;理解基本的数字逻辑概念&#xff0c;…

基于danceTrack相关论文代码列表

文章目录 数据集下载2023Observation-Centric SORT: Rethinking SORT for Robust Multi-Object Tracking 数据集下载 https://github.com/DanceTrack/DanceTrack 2023 Observation-Centric SORT: Rethinking SORT for Robust Multi-Object Tracking code: https://github.c…

微型导轨:光学仪器精准定位的支撑者

微型导轨是指宽度在25mm以下的导轨系统&#xff0c;通常由导轨和滑块组成&#xff0c;具有体积小、重量轻、精度高、噪音低、寿命长等特点。主要用于支撑和定位光学元件&#xff0c;如镜子、透镜、滤光片等。微型导轨通过提供高精度的运动控制&#xff0c;‌有利于提高设备的性…

重磅发布 |《一本书讲透数据资产入表》在全球数据资产大会上发布

2024年8月2日&#xff0c;全球数据资产大会在厦门举行&#xff0c;数据资产管理标杆厂商亿信华辰正式发布全新力作《一本书讲透数据资产入表》&#xff0c;荣获“数据资产十大先锋机构”&#xff0c;并发表主题演讲&#xff0c;展现其在数据资产管理领域的领军风采与创新实力。…

macOS Java多版本管理工具

macOS Java多版本管理工具 可以使用 sdkman&#xff0c;也可以使用jenv 能用 sdkman 就建议使用 sdkman &#xff0c;用不了就使用 jenv # sdkman的安装及使用 蚁景网安学院-一个开放的网络安全交流学习论坛 # jenv 的安装及使用 # 安装JDK8 下载 JDK8 JDK8下载页面&…

Ubuntu环境安装MySQL

Ubuntu环境安装MySQL 1. 访问下载界面并下载发布包2. 安装发布包3. 安装MySQL 1. 访问下载界面并下载发布包 下载地址 也可直接去mysql.com官网下载 这里如果要下载其他版本的或可以去http://repo.mysql.com/这个网页查询相关的版本。 2. 安装发布包 使用切换到root用户…

美元兑人民币汇率的变化,对A股直接影响是什么

美元兑人民币汇率的变化对A股的直接影响是复杂且多面的&#xff0c;主要体现在以下几个方面&#xff1a; 一、市场情绪与投资者信心 汇率波动引发市场担忧&#xff1a;当美元兑人民币汇率大幅波动时&#xff0c;尤其是人民币贬值&#xff0c;可能会引发市场担忧&#xff0c;影…

数据复盘“黑色星期一”:加密市场震荡,代币表现如何?

8月5日的“黑色星期一”成为了全球金融市场的动荡日&#xff0c;这一波及到加密市场的剧烈震荡导致了大量清算事件和代币的暴跌。本文将通过数据复盘&#xff0c;分析这一事件中加密货币的表现&#xff0c;并探讨未来市场的可能走向。 一、暴跌中的惨痛数据 在“黑色星期一”事…

Linux初次体验

Linux系统也是的命令字符也是多的离谱&#xff0c;本来不想写的就顺便写写吧 首先 ctrlaltT 打开终端 这里我就创建了一个文件&#xff0c;test&#xff0c;使用 vi 文件名.c 默认模式是命令行模式 无法写代码的 输入 i 后进入输入模式&#xff0c;开始写代码 退出输入模式…

032_java.util.concurrent.ConcurrentHashMap

继承体系 HashMap是我们用得非常频繁的一个集合&#xff0c;但是由于它是非线程安全的&#xff0c;在多线程环境下&#xff0c;put操作是有可能产生死循环的&#xff0c;导致CPU利用率接近100%。为了解决该问题&#xff0c;提供了Hashtable和Collections.synchronizedMap(hashM…