spring 解决循环依赖

在 spring 框架中,我们知道它是通过三级缓存来解决循环依赖的,那么它具体是怎么实现的,以及是否必须需要三级缓存才能解决循环依赖,本文来作相关介绍。

具体实现

先来看看它的三级缓存到底是什么,先看如下代码:

// DefaultSingletonBeanRegistry
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Quick check for existing instance without full singleton lock
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		singletonObject = this.earlySingletonObjects.get(beanName);
		if (singletonObject == null && allowEarlyReference) {
			synchronized (this.singletonObjects) {
				// Consistent creation of early reference within full singleton lock
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					singletonObject = this.earlySingletonObjects.get(beanName);
					if (singletonObject == null) {
						ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
						if (singletonFactory != null) {
							singletonObject = singletonFactory.getObject();
							this.earlySingletonObjects.put(beanName, singletonObject);
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	return singletonObject;
}
  • 一级缓存:singletonObjects
  • 二级缓存:earlySingletonObjects
  • 三级缓存:singletonFactories

获取实例对象的时候,先去一二级缓存查找,如果没找到,allowEarlyReference 为 true,会对 singletonObjects 加锁,并进行二次查找,还是找不到,会通过三级缓存,获取一个 singletonFactory,通过 singletonFactory 获取实例对象。

下面来看看这个 singletonFactory 是什么?

在 AbstractAutowireCapableBeanFactory#doCreateBean 中有这么一段代码:

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));
}
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);
		}
	}
}

放入了一个 lambda 表达式,即在实例化完一个实例对象之后,将这个半成品通过 singletonFactory 提前暴露出去,接着再进行 populateBean、applyPropertyValues 调用。

实例对象创建过程

下面来看看实例对象创建时的具体过程 ,如上图所示,A 引用 B,B 引用 A。

实例化完 A 之后,将 A 提前暴露,接着进行属性填充,此时发现需要 B,而 B 还未创建,接着去创建 B,实例化完 B 之后,也将 B 暴露出去,接着进行属性填充,此时需要 A,接着去找 A,由于 A 已经提前暴露了,会获取到 singletonFactory,接着调入 getEarlyBeanReference。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
			exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
		}
	}
	return exposedObject;
}

存在 SmartInstantiationAwareBeanPostProcessor 接口子类时,调用对应实现的 getEarlyBeanReference,主要是 AOP 增强时使用。

// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	this.earlyProxyReferences.put(cacheKey, bean);
	return wrapIfNecessary(bean, beanName, cacheKey);
}

将实例化后的半成品放入 earlyProxyReferences,接着对其进行包装,即 AOP增强。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

	// Create proxy if we have advice.
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	if (specificInterceptors != DO_NOT_PROXY) {
		this.advisedBeans.put(cacheKey, Boolean.TRUE); // 需要拦截,标记
		Object proxy = createProxy(
				bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}

	this.advisedBeans.put(cacheKey, Boolean.FALSE); // 不需要拦截,标记
	return bean;
}

如果 A 中的方法需要增强,getAdvicesAndAdvisorForBean 方法会遍历注册的 beanDefinitionNames,找到匹配的 advisorNames,接着遍历 advisorsNames,创建对应的 advisor,填充属性需要 advice,进行 advice 的创建,最终完成 advisor 的创建,此时创建的 advisor 只是候选,具体能否为当前 A 使用,还需进行匹配判断,最后将匹配的 advisor 返回,即 specificInterceptors,接着对 A 进行标记,创建 proxy 返回。

返回的 proxy 会放入二级缓存 earlySingletonObjects,并将三级缓存中的 singletonFactory 删除。

拿到对这个半成品创建的 proxy,完成 B 的属性填充,接着进行 B 的初始化,如果 B 也需要 AOP 增强,执行 AbstractAutoProxyCreator#postProcessAfterInitialization。

// AbstractAutoProxyCreator
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

最后返回的就是增强后的 B,接着将 B 填充进 A,完成 A 的属性填充。进行 A 的初始化,此时调用 postProcessAfterInitialization 传入的参数 bean 还是一开始实例化的 bean,并不是创建 B 时增强的 proxyA,从 earlyProxyReferences 移除后与传入的参数 bean 相等,不会进行二次增强。

现在有一个问题,出现了两个 A,一个是普通的 A,一个是增强后的 proxyA,接着看看 spring 中的处理。

if (earlySingletonExposure) {
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
		if (exposedObject == bean) {
			exposedObject = earlySingletonReference;
		}
	}
}

通过 getSingleton 再获取一次,此时会从二级缓存获取到增强后的 proxyA,接着判断成立,会将proxyA 作为 exposedObject 返回,并将其注册到一级缓存 singletonObjects。

现在,我们可以知道每个缓存里存放的具体是什么了:

  • 一级缓存 singletonObjects:完全的单例对象
  • 二级缓存 earlySingletonObjects:半成品的 AOP 的增强对象
  • 三级缓存 singletonFactories:获取半成品对象 或着 生成半成品 AOP 增强的工厂方法

由此可知,解决循环依赖,并不是必须需要三级缓存,如果没有 AOP 增强,只采用 singletonObjects 和 singletonFactories 两级缓存就可以解决循环依赖。而二级缓存 earlySingletonObjects,专为解决 AOP 而存在。

并且,对于A,返回 proxyA,对于 B,填充的就是 proxyA,而 proxyA 又是 A 子类,这样 proxyA 也就拥有了 A 中填充的属性。即 proxyA 只是对方法进行增强,而属性填充还是在 A 中进行。这样也体现了类的单一职责原则。

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

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

相关文章

Nginx网站服务【☆☆☆】

市面上常用Linux的web服务器&#xff1a;apache、Nginx。 apache与nginx的区别&#xff1f; 最核心的区别在于NGINX采用异步非阻塞机制&#xff0c;多个连接可以对应一个进程&#xff1b;apache采用的是同步阻塞多进程/线程模型&#xff0c;一个连接对应一个进程。apache美国…

c++简略实现共享智能指针Shared_Ptr<T>

重点&#xff1a; 1.引用计数在堆上&#xff08;原本应为原子变量&#xff09; 2.引用计数增加减少需要加锁保证线程安全。 3.内部实现Release函数用于释放资源 4.未实现&#xff0c;增加自定义删除器可以将Release修改为模板函数&#xff0c;传入可调用参数。对于shared_p…

tomcat服务器之maxHttpHeaderSize

背景&#xff1a;在OA流程表单中&#xff0c;填写了200条数据&#xff0c;一提交&#xff0c;秒报400错误&#xff0c;且请求没有打到后端中&#xff08;无报错日志&#xff09;&#xff0c;一开始以为是谷歌浏览器的问题&#xff0c;可百度上关于这个错误的解决方案都是清除缓…

docker实战流程:

Docker-compose是docker官方的开源项目&#xff0c;负责实现对docker容器的集群的快速编排&#xff08;通过yaml文件docker-compose.yml管理写好容器之间的调用关系只需一个命令就能实现容器的通识开启或关闭&#xff09;。 类比spring容器&#xff0c;spring管理的是bean而do…

vue3+uniapp

1.页面滚动 2.图片懒加载 3.安全区域 4.返回顶部&#xff0c;刷新页面 5.grid布局 place-self: center; 6.模糊效果 7.缩放 8.微信小程序联系客服 9.拨打电话 10.穿透 11.盒子宽度 12.一般文字以及盒子阴影 13.选中文字 14.顶部安全距离 15.onLoad周期函数在setup语法糖执行后…

PVE安装虚拟主机

本文记录PVE安装其他虚拟主机的步骤&#xff0c;以安装win-server为例。裸机安装PVE则不是本文主题。 准备文件 获取Windows系统镜像 win server镜像可以从官网获取普通Windows镜像可从MSDN获取此外&#xff0c;安装Windows系统还需要从PVE下载特殊驱动 获取Windows必要驱动 …

数据库(25)——多表关系介绍

在项目开发中&#xff0c;进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;各个表之间的结构基本上分为三种&#xff1a;一对多&#xff0c;多对多&#xff0c;一对一。 一对多 例如&#xff0c;一个学校可以有…

PromptPerfect:AI Prompt生成与优化专家

PromptPerfect&#xff1a;AI Prompt生成与优化专家 PromptPerfect是一款专业的AI Prompt生成与优化工具&#xff0c;旨在帮助用户解锁和最大化GPT-4、ChatGPT、Midjourney等先进AI模型的潜力。它通过智能生成和精细优化Prompt&#xff0c;帮助用户获得更精确、更相关、更高效…

Aws EC2,kubeadm方式安装kubernetes(k8s)

版本 docker版本&#xff1a;20.10.25 k8s版本&#xff08;kubeadm&#xff0c;kubelet和kubectl&#xff09;&#xff1a;1.20.10-0 初始化 # 禁用 SELinux sudo setenforce 0 sudo sed -i s/^SELINUXenforcing$/SELINUXpermissive/ /etc/selinux/config# 关闭防火墙 sudo …

题号:BC19 题目:反向输出一个四位数

题号&#xff1a;BC19 题目&#xff1a;反向输出一个四位数 废话不多说&#xff0c;上题目&#xff1a; 解题思路&#xff1a; 我们发现可以用%和/两个操作符就可以解决。 代码如下: int main() {int a 0;scanf("%d ",& a);while (a){printf("%d "…

【实物+仿真设计】基于单片机的物流皮带传输监控系统设计

《基于单片机的物流皮带传输监控系统设计 实物仿真》 整体功能&#xff1a; 本设计采用以单片机为核心控制器&#xff0c;以及传感器检测部分作为输入部分&#xff0c;以报警、显示、洒水、排烟、电机停止模块作为输出部分&#xff0c;构成整个物流皮带传输监控系统。 本设计…

Kolmogorov–Arnold Networks (KAN) 即将改变 AI 世界

目录 一、说明 二、KAN介绍 2.1 什么是 Kolmogorov-Arnold Networks &#xff08;KAN&#xff09;&#xff1a; 2.2 KAN 的秘诀&#xff0c;Splines&#xff01; 2.3 了解KAN工作的最简单方法 三、KAN的主要优点 四、KAN 的 Python 实现 &#xff08;PyKAN&#xff09; 4.1 …

神经网络 | 深度学习背后的数学

神经网分析 机器学习处理的是数据&#xff0c;通过学习输入的数据&#xff0c;从而建立模型&#xff0c;以便预测新的数据的输出 按照类型可以进行如下分类 监督分类 非监督分类 强化学习 神经元 生物学中&#xff0c;人的大脑是由多个神经元互相连接形成网络而构成的。也…

访问vue乱码解决

前情提要&#xff1a; 前端 vue工程npm run build生成的dist静态文件 后端 springboot 后端mvn install 生成war包&#xff0c;前端dist放进war包 报错&#xff1a; 页面访问白屏 报错如图 可以看到chunk-vendors.xxxx.js出现了中文乱码&#xff0c;这个js文件是vue build生成的…

2024版本---LabVIEW 软件安装及使用教程

目录 第1章 LabVIEW 软件安装及使用教程 1. 简介 2. 安装教程 2.1 下载 LabVIEW 2024 版本 2.2 安装 LabVIEW 3. 激活 LabVIEW 4. LabVIEW 基本使用教程 4.1 用户界面介绍 4.2 创建一个简单的 VI&#xff08;虚拟仪器&#xff09; 4.3 数据采集示例 5. 进阶功能介绍…

秋招突击——算法打卡——6/5——提高{(状态机模型)股票买卖、(单调队列优化DP)最大子序列和}——新做:{考试的最大困扰度}

文章目录 提高(状态机模型)股票买卖IV思路分析实现代码参考代码 新作考试的最大困扰度个人实现参考思路 总结 提高 (状态机模型)股票买卖IV 上一次的思路总结&#xff0c;上次写的时候忘记总结了&#xff0c;现在重新画一下图 思路分析 这道题是一个经典的状态机模型&#…

【数据挖掘】学习笔记

文章目录 第一章 引论1.1 为什么进行数据挖掘1.2 什么是数据挖掘&#xff1f;1.3 可以挖掘什么类型的数据1.4 可以挖掘什么类型的模式1.4.1 类/概念描述&#xff1a;特征化和区分1.4.2 挖掘频繁模式、关联规则和相关性1.4.3 用于预测分析的分类和回归1.4.4 聚类分析1.4.5 离群点…

【C语言之排序】-------六大排序

作者主页&#xff1a;作者主页 数据结构专栏&#xff1a;数据结构 创作时间 &#xff1a;2024年5月18日 前言&#xff1a; 今天我们就给大家带来几种排序的讲解&#xff0c;包括冒泡排序&#xff0c;插入排序&#xff0c;希尔排序&#xff0c;选择排序&#xff0c;堆排序&…

计算机网络ppt和课后题总结(下)

常用端口总结 计算机网络中&#xff0c;端口是TCP/IP协议的一部分&#xff0c;用于标识运行在同一台计算机上的不同服务。端口号是一个16位的数字&#xff0c;范围从0到65535。通常&#xff0c;0到1023的端口被称为“熟知端口”或“系统端口”&#xff0c;它们被保留给一些标准…

【Python数据类型的奥秘】:构建程序基石,驾驭信息之海

文章目录 &#x1f680;Python数据类型&#x1f308;1. 基本概念⭐2. 转化&#x1f44a;3. 数值运算&#x1f4a5;4. 数值运算扩展(math库常用函数) &#x1f680;Python数据类型 &#x1f308;1. 基本概念 整数&#xff08;int&#xff09;&#xff1a;整数是没有小数部分的数…