spring 解决循环依赖

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

具体实现

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

// DefaultSingletonBeanRegistry
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject 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 locksingletonObject = 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 中进行。这样也体现了类的单一职责原则。

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

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

相关文章

Unity动画录制工具在运行时录制和保存模型骨骼运动的方法录制动画给其他角色模型使用支持JSON、FBX等格式

如果您正在寻找一种在运行时录制和保存模型骨骼运动的方法&#xff0c;那么此插件是满足您需求的完美解决方案。 实时录制角色运动 将录制到的角色动作转为动画文件 将录制好的动作给新的角色模型使用&#xff0c;完美复制 支持导出FBX格式 操作简单&#xff0c;有按钮界面…

【机器学习】我们该如何评价GPT-4o?GPT-4o的技术能力分析以及前言探索

目录 &#x1f926;‍♀️GPT-4o是什么&#xff1f; &#x1f68d;GPT-4o的技术能力 1. 自然语言理解 2. 自然语言生成 3. 对话系统 4. 语言翻译 5. 文本纠错 6. 知识问答 7. 定制和微调 8. 透明性和可解释性 9. 扩展性 &#x1f690;版本对比分析 1. GPT-4标准版 …

像素蛋糕Photoshop颜色导出不一致问题分析与解决

问题点&#xff1a;发现用像素蛋糕修完图明天应该为最右边图片显示 模特应该是白皙的&#xff0c;但是导出图片无论是否勾选SRGB都表现的为种间图片颜色一样 饱和度巨高。 问题分析&#xff1a;那这一定是颜色配置文件出现问题&#xff0c;找到客服表示可以去PS打开看是否与预…

Linux之进程信号详解【上】

&#x1f30e; Linux信号详解 文章目录&#xff1a; Linux信号详解 信号入门 技术应用角度的信号 信号及信号的产生       信号的概念       信号的处理方式 信号的产生方式         键盘产生信号         系统调用产生信号         软件…

P1072 [NOIP2009 提高组] Hankson 的趣味题

Hankson 的趣味题 这题要有思维&#xff01;对。数论&#xff01;最大公约数与最小公倍数。 用LaTex写公式&#xff0c;真的麻烦&#xff01;wcnmd!,,,,,,be---- 于是我用手写了&#xff1a; 大功告成&#xff01;上马&#xff01; #include<cstdio> using namespace …

MyBatis插件机制

MyBatis插件机制是该框架提供的一种灵活扩展方式&#xff0c;允许开发者在不修改框架源代码的情况下对MyBatis的功能进行定制和增强。这种机制主要通过拦截器&#xff08;Interceptor&#xff09;实现&#xff0c;使得开发者可以拦截和修改MyBatis在执行SQL语句过程中的行为。 …

两轮自平衡小车资料(L298N 模块原理图及使用说明+c源码)

本文详细介绍了基于STM32微控制器的两轮自平衡小车的设计与实现过程。内容包括小车的硬件选型、电路设计、软件编程以及PID控制算法的应用。通过陀螺仪和加速度计获取小车的姿态信息&#xff0c;利用PID控制算法调整电机输出&#xff0c;实现小车的自主平衡。此外&#xff0c;还…

[图解]企业应用架构模式2024新译本讲解12-领域模型5

1 00:00:00,560 --> 00:00:04,690 刚才是往那个表里面添加数据了 2 00:00:04,700 --> 00:00:07,960 相当于&#xff0c;或者往这个合同里面添加数据了 3 00:00:08,430 --> 00:00:09,530 现在要查询怎么办 4 00:00:09,900 --> 00:00:10,930 跟前面一样 5 00:00:…

简单的基于threejs和BVH第一人称视角和第三人称视角控制器

渲染框架是基于THREE,碰撞检测是基于BVH。本来用的是three自带的octree结构做碰撞发现性能不太好 核心代码&#xff1a; import * as THREE from three import { RoundedBoxGeometry } from three/examples/jsm/geometries/RoundedBoxGeometry.js; import { MeshBVH, MeshBVHHe…

计算机系统基础笔记(12)——控制

前言 在持续输出ing 一、条件码 1.处理器状态&#xff08;x86-64&#xff0c;部分的&#xff09; 当前程序的执行信息 ◼ 临时数据 ◼ 运行时栈的位置&#xff08;栈顶&#xff09; ◼ 当前代码控制点的位置&#xff08;即将要执行的指令地址&#xff09; ◼ 最近一次指令执…

【C++关键字】auto的使用(C++11)

auto的使用&#xff08;C11&#xff09; auto关键字auto的使用细则auto使用场景 随着程序的复杂化&#xff0c;程序中用到的类型也越来越复杂化&#xff0c;经常体现在&#xff1a; 1.类型难以拼写 2.含义不明确导致容易出错 在C语言阶段处理这类问题的方法&#xff0c;可以使…

拉格朗日乘子将不等式约束转化为等式约束例子

拉格朗日乘子将不等式约束转化为等式约束例子 在优化问题中,常常需要将不等式约束转化为等式约束。使用拉格朗日乘子法,可以通过引入松弛变量将不等式约束转换为等式约束,然后构造拉格朗日函数进行求解。 拉格朗日乘子法简介 拉格朗日乘子法是求解带约束优化问题的一种方…

【吊打面试官系列-Mysql面试题】BLOB 和 TEXT 有什么区别 ?

大家好&#xff0c;我是锋哥。今天分享关于 【BLOB 和 TEXT 有什么区别&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; BLOB 和 TEXT 有什么区别 &#xff1f; BLOB 是一个二进制对象&#xff0c;可以容纳可变数量的数据。TEXT 是一个不区分大小写的 BLOB。 1…

【调整堆】(C++ 代码实现 注释详解)

自定义结构体&#xff1a; #define sz 105 typedef struct node{int length;int l[sz]; }SqList; 调整堆的函数&#xff1a; HeapAdjust函数思路说明&#xff1a; //目标&#xff1a;将以s为根的子树调整为大根堆 //具体操作&#xff1a;将路径上比s大的都往上移动,s往下移…

gRPC(狂神说)

gRPC&#xff08;狂神说&#xff09; 视频地址&#xff1a;【狂神说】gRPC最新超详细版教程通俗易懂 | Go语言全栈教程_哔哩哔哩_bilibili 1、gRPC介绍 单体架构 一旦某个服务宕机&#xff0c;会引起整个应用不可用&#xff0c;隔离性差只能整体应用进行伸缩&#xff0c;浪…

【C++ STL】模拟实现 string

标题&#xff1a;【C :: STL】手撕 STL _string 水墨不写bug &#xff08;图片来源于网络&#xff09; C标准模板库&#xff08;STL&#xff09;中的string是一个可变长的字符序列&#xff0c;它提供了一系列操作字符串的方法和功能。 本篇文章&#xff0c;我们将模拟实现STL的…

ipables防火墙

一、Linux防火墙基础 Linux 的防火墙体系主要工作在网络层&#xff0c;针对 TCP/IP 数据包实施过滤和限制&#xff0c;属于典 型的包过滤防火墙&#xff08;或称为网络层防火墙&#xff09;。Linux 系统的防火墙体系基于内核编码实现&#xff0c; 具有非常稳定的性能和高效率&…

VB7/64位VB6开发工具office插件开发-twinbasic

全新的VB7&#xff0c;twinbasic&#xff0c;支持64位开发&#xff0c;支持EXCEL插件开发&#xff0c;老外连续3年闭关修练终成正果 官方最新版下载&#xff1a;https://github.com/twinbasic/twinbasic/releases 汉化工具用法&#xff1a;把工具和Lang_Tool目录复制到Twinbasi…

SAP PP学习笔记18 - MTO(Make-to-Order):按订单生产(受注生産) 的策略 20,50,74

前面几章讲了 MTS&#xff08;Make-to-Stock&#xff09;按库存生产的策略&#xff08;10&#xff0c;11&#xff0c;30&#xff0c;40&#xff0c;70&#xff09;。 SAP PP学习笔记14 - MTS&#xff08;Make-to-Stock) 按库存生产&#xff08;策略10&#xff09;&#xff0c;…

ChatTTS 开源文本转语音模型本地部署、API使用和搭建WebUI界面(建议收藏)

ChatTTS&#xff08;Chat Text To Speech&#xff09;是专为对话场景设计的文本生成语音(TTS)模型&#xff0c;特别适用于大型语言模型(LLM)助手的对话任务&#xff0c;以及诸如对话式音频和视频介绍等应用。它支持中文和英文&#xff0c;还可以穿插笑声、说话间的停顿、以及语…