详解Spring的循环依赖问题、三级缓存解决方案源码分析
0、基础:Bean的生命周期
在Spring中,由于IOC的控制反转,创建对象不再是简单的new出来,而是交给Spring去创建,会经历一系列Bean的生命周期才创建出相应的对象。而循环依赖问题也是由Bean的生命周期过程导致的问题,因此我们首先需要了解Bean的生命周期。
Bean的生命周期可以概括为4步:
实例化----属性注入----初始化----销毁
详细的讲,步骤如下:
******************************************实例化123*******************************************
- 定位:Spring容器会根据配置文件(如XML、注解等)或编程式配置来确定需要创建的Bean。
加载:Spring容器会加载配置文件并解析其中的Bean定义,将其转换为内部数据结构,例如BeanDefinition。
实例化:在实例化阶段,Spring容器会根据Bean定义中的信息创建Bean的实例。这个过程可以通过构造函数实例化、工厂方法实例化或者通过反射机制来实现。*****************************************属性注入4*********************************************
属性注入:在实例化Bean之后,Spring容器会对Bean进行属性注入。这可以通过setter方法注入、构造函数参数注入或字段注入等方式来完成。
****************************************初始化5~9********************************************Aware接口回调:如果Bean实现了Spring的Aware接口,容器会通过回调方式将一些特殊的资源注入到Bean中。例如,如果Bean实现了BeanFactoryAware接口,容器会将当前的BeanFactory实例注入到Bean中。
初始化前回调:如果Bean实现了InitializingBean接口,容器会在初始化之前调用它的
afterPropertiesSet()
方法,给Bean一个机会执行一些初始化操作。同时,Spring容器还支持使用自定义的初始化方法。初始化后回调:如果Bean配置了初始化回调方法,容器会调用该方法进行一些自定义的初始化逻辑处理。
后置处理器(BeanPostProcessor):Spring容器会调用注册的Bean后置处理器对Bean进行加工和增强。例如,可以通过AOP技术在这个阶段为Bean动态生成代理对象。
完成:至此,Bean已经成功创建,并且已经完成了所有的初始化过程。此时可以将Bean提供给其他对象使用。
****************************************销毁10、11******************************************销毁前回调(PreDestroy):在容器关闭之前,调用Bean的销毁前回调方法,执行一些清理操作和释放资源的任务。
销毁:容器关闭时,销毁所有Bean实例,包括调用相应Bean的销毁方法,进行最终的清理和资源释放。
1、循环依赖问题
例如下面的代码,A和B类就构成了循环依赖,原因如下:
@Component
public class A {@Autowiredprivate B b;
}
@Component
public class B{@Autowiredprivate A a;
}
创建Bean的步骤:
- Spring 扫描 class 得到 BeanDefinition;
- 根据得到的 BeanDefinition 去生成 bean;
- 首先根据 class 推断构造方法;
- 根据推断出来的构造方法,反射,得到一个对象(我们称为原始对象);
- 填充原始对象中的属性(依赖注入);
- 如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象;
- 把最终生成的代理对象放入单例池(源码中叫做 singletonObjects)中,下次 getBean 时就直接从单例池拿即可;
对于上述步骤的第4步, 得到原始对象后需要注入属性,A 类中存在一个 B 类的 b 属性,此时就会根据 b 属性的类型和属性名去 BeanFactory 中去获取 B 类所对应的单例bean。
如果此时 B 类在 BeanFactory 中还没有生成对应的 Bean,那么就需要去生成,就会经过 B 的 Bean 的生命周期,也就会同样的,需要A类的Bean,就发生了循环依赖,导致A和B的bean都创建不出来。
概括而言:
A Bean创建–>依赖了 B 属性–>触发 B Bean创建—>B 依赖了 A 属性—>需要 A Bean(但A Bean还在创建过程中)
然而实际上,Spring通过三级缓存的方式自动解决了这个问题 。
2、三级缓存的引入
2.1 非AOP情况下的解决方案
根据上文的分析我们发现,出现循环依赖的根本原因,是B的Bean需要注入A属性的时候,Bean A还没有创建出来,导致的。那么相应的,只要:
在进行依赖注入之前,先把 A 的原始 Bean 放入缓存(提早暴露,只要放到缓存了,其他 Bean 需要时就可以从缓存中拿了,这个缓存就应该是earlySingletonObjects),放入缓存后,再进行依赖注入。
由于提前暴露,在创建B的Bean过程中,当需要注入A的属性时,就可以从缓存中拿到A提前暴露的原始对象(还不是最终Bean),就解决了问题。关键在于全程只有一个A的原始对象,其后续的生命周期没有变化。
如下图所示:
2.2 三级缓存具体
因此,对于不同时期的Bean,如原始Bean、完整周期的Bean,需要不同的缓存来存放,底层源码中有三级缓存:
/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
- 一级缓存:singletonObjects;缓存的是已经经历了完整生命周期的bean对象。
- 二级缓存:earlySingletonObjects;比 singletonObjects 多了一个 early ,表示缓存的是早期的 bean对象(原始对象)。早期指的是 Bean 的生命周期还没走完就把这个 Bean 放入了 earlySingletonObjects
- 三级缓存:singletonFactories;缓存的是 ObjectFactory,表示对象工厂,用来创建某个对象的。
3、有AOP情况下使用singletonFactories
3.1 引入三级缓存
看似我们只需要1、2级缓存就能够解决问题了,为什么需要三级缓存呢?
这就需要考虑到AOP代理对象的问题了:
上文的红字提到,之所以能够提前暴露,是因为假定的A的原始对象始终是同一个对象,但如果有AOP的情况下呢?我们考虑这样的场景:
按照上文的分析,假设创建B的bean过程中,注入了A的原始对象属性。然后,A的原始对象采用AOP产生了一个代理对象,即,A的Bean变成了 AOP 之后的代理对象。而B中的 属性a对应的并不是 AOP 之后的代理对象,而仍然是原始对象。
也就是说,这种情况下,B 依赖的 A 和最终的 A 不是同一个对象!
而解决这个问题的方法,就是引入三级缓存的 singletonFactories
3.2 三级缓存具体解析
实际上,在有AOP的情况下,Spring并没有像第2节所说,直接将示例缓存到二级缓存,而是生成完原始对象之后”多此一举“地将实例先封装到objectFactory中,在需要引用的时候再通过singletonFactory.getObject()获取。
跟进getObject()方法,其实执行了getEarlyBeanReference这个关键方法。
this.addSingletonFactory(beanName, () -> {return this.getEarlyBeanReference(beanName, mbd, bean);});
也就是说,Spring将当前bean缓存到earlyProxyReferences
中,标识提前曝光的bean。而wrapIfNecessary
是用于Spring AOP自动代理的,也就是说在被提前引用前,进行了AOP代理,并得到了代理对象。
此时earlySingletonObjects 缓存中的对象就是代理对象了!
因此,假设此时有其他对象依赖了A,就可以从earlySingletonObjects中获取到A原始对象的代理对象了,并且和A是同一个对象,实现了目标。
3.3 后续依赖问题
当 B 创建完了之后,A 继续进行生命周期,而 A 在完成属性注入后,会按照它本身的逻辑去进行AOP,而此时我们知道 A 原始对象已经经历过了 AOP ,所以对于 A 本身而言,不会再去进行 AOP了,那么怎么判断一个对象是否经历过了 AOP 呢?
注意postProcessAfterInitialization方法,会当前beanName是否在earlyProxyReferences中,如果在就AOP过了,不在则执行AOP方法。
此时对于Bean A对象而言已经完成创建了,可以把它放入缓存singletonObjects中了,因此从earlySingletonObjects 中得到代理对象,然后入 singletonObjects 中。
至此,整个循环依赖解决完毕。
4、总结
这里引用看到的一篇写的很清晰的博客http://t.csdn.cn/GeHkA的图来说明具体流程:
对于三级缓存的singletonFactories,总结而言:
缓存的是一个 ObjectFactory ,主要用来去生成原始对象进行了 AOP之后得到的「代理对象」。
在每个 Bean 的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到:
(1)如果没有出现循环依赖依赖本 bean,那么这个工厂无用,本 bean 按照自己的生命周期执行,执行完后直接把本 bean 放入 singletonObjects 中即可(对应本文章的第1节)
(2)如果出现了循环依赖依赖了本 bean,则:
<2.1>如果有 AOP 的话,另外那个 bean 执行 ObjectFactory 提交得到一个 AOP 之后的代理对象。(对应本文章第3节)
<2.2>如果无需 AOP ,则直接得到一个原始对象。(对应本文章第2节)
相关文章:

详解Spring的循环依赖问题、三级缓存解决方案源码分析
0、基础:Bean的生命周期 在Spring中,由于IOC的控制反转,创建对象不再是简单的new出来,而是交给Spring去创建,会经历一系列Bean的生命周期才创建出相应的对象。而循环依赖问题也是由Bean的生命周期过程导致的问题&#…...
oracle分析函数学习
0、建表及插入测试数据 --CREATE TEST TABLE AND INSERT TEST DATA. create table students (id number(15,0), area varchar2(10), stu_type varchar2(2), score number(20,2));insert into students values(1, 111, g, 80 ); insert into students values(1, 111, j, 80 ); …...
代码随想录训练营day17|110.平衡二叉树 257. 二叉树的所有路径 404.左叶子之和 v...
TOC 前言 代码随想录算法训练营day17 一、Leetcode 110.平衡二叉树 1.题目 给定一个二叉树,判断它是否是高度平衡的二叉树。 本题中,一棵高度平衡二叉树定义为: 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 示例 1&#x…...
C# Thread用法
C# 中的线程(Thread)是一种并发执行的机制,允许同时执行多个代码块,从而提高程序的性能和响应性。下面是关于如何使用 C# 线程的一些基本用法: 1. 创建线程: 使用 System.Threading 命名空间中的 Thread 类…...

新榜 | CityWalk本地生活商业价值洞察报告
如果说现在有人问,最新的网络热词是什么? “CityWalk”,这可能是大多数人的答案。 近段时间,“CityWalk”刷屏了各种社交媒体,给网友们带来了一场“城市漫步”之旅。 脱离群体狂欢,这个在社交媒体引发热议的词汇背后又…...

LVS负载均衡集群-NAT模式部署
集群 集群:将多台主机作为一个整体,然后对外提供相同的服务 集群使用场景:高并发的场景 集群的分类 1.负载均衡器集群 减少响应延迟,提高并发处理的能力 2,高可用集群 增强系统的稳定性可靠性&…...
C++学习笔记总结练习:effective 学习日志
准则 1.少使用define define所定义的常量会在预处理的时候被替代,出错编译器不容易找到错误。而且还没有作用范围限制,推荐使用constdefine宏定义的函数,容易出错,而且参数需要加上小括号,推荐使用inline有的类中例如…...

Vue教程(五):样式绑定——class和style
1、样式代码准备 样式提前准备 <style>.basic{width: 400px;height: 100px;border: 1px solid black;}.happy{border: 4px solid red;background-color: rgba(255, 255, 0, 0.644);background: linear-gradient(30deg, yellow, pink, orange, yellow);}.sad{border: 4px …...

开放网关架构演进
作者:庄文弘(弘智) 淘宝开放平台是阿里与外部生态互联互通的重要开放途径,通过开放的产品技术把阿里经济体一系列基础服务,像水、电、煤一样输送给我们的商家、开发者、社区媒体以及其他合作伙伴,推动行业的…...

torch一些操作
Pytorch文档 Pytorch 官方文档 https://pytorch.org/docs/stable/index.html pytorch 里的一些基础tensor操作讲的不错 https://blog.csdn.net/abc13526222160/category_8614343.html 关于pytorch的Broadcast,合并与分割,数学运算,属性统计以及高阶操作 https://blog.csd…...

ICCV23 | Ada3D:利用动态推理挖掘3D感知任务中数据冗余性
论文地址:https://arxiv.org/abs/2307.08209 项目主页:https://a-suozhang.xyz/ada3d.github.io/ 01. 背景与动因 3D检测(3D Detection)任务是自动驾驶任务中的重要任务。由于自动驾驶任务的安全性至关重要(safety-critic),对感知算法的延…...

软件工程模型-架构师之路(四)
软件工程模型 敏捷开发: 个体和交互 胜过 过程和工具、可以工作的软件 胜过 面面俱到的文件、客户合作胜过合同谈判、响应变化 胜过 循序计划。(适应需求变化,积极响应) 敏捷开发与其他结构化方法区别特点:面向人的…...

ubuntu20.04共享文件夹—— /mnt/hgfs里没有共享文件夹
参考文章:https://blog.csdn.net/Edwinwzy/article/details/129580636 虚拟机启用共享文件夹后,/mnt/hgfs下面为空,使用 vmware-hgfsclient 查看设置的共享文件夹名字也是为空。 解决方法: 1. 重新安装vmware tools. 在菜单…...

Redis中的有序集合及其底层跳表
前言 本文着重介绍Redis中的有序集合的底层实现中的跳表 有序集合 Sorted Set Redis中的Sorted Set 是一个有序的无重复值的集合,他底层是使用压缩列表和跳表实现的,和Java中的HashMap底层数据结构(1.8)链表红黑树异曲同工之妙…...

js 小程序限流函数 return闭包函数执行不了
问题: 调用限流 ,没走闭包的函数: checkBalanceReq() loadsh.js // 限流 const throttle (fn, context, interval) > {console.log(">>>>cmm throttle", context, interval)let canRun…...

【数据结构】堆的初始化——如何初始化一个大根堆?
文章目录 源码是如何插入的?扩容向上调整实现大根堆代码: 源码是如何插入的? 扩容 在扩容的时候,如果容量小于64,那就2倍多2的扩容;如果大于64,那就1.5倍扩容。 还会进行溢出的判断,…...

【韩顺平 零基础30天学会Java】程序流程控制(2days)
day1 程序流程控制:顺序控制、分支控制、循环控制 顺序控制:从上到下逐行地执行,中间没有任何判断和跳转。 Java中定义变量时要采用合法的前向引用。 分支控制if-else:单分支、双分支和多分支。 单分支 import java.util.Scann…...

从入门到精通Python隧道代理的使用与优化
哈喽,Python爬虫小伙伴们!今天我们来聊聊如何从入门到精通地使用和优化Python隧道代理,让我们的爬虫程序更加稳定、高效!今天我们将对使用和优化进行一个简单的梳理,并且会提供相应的代码示例。 1. 什么是隧道代理&…...

19万字智慧城市总体规划与设计方案WORD
导读:原文《19万字智慧城市总体规划与设计方案WORD》(获取来源见文尾),本文精选其中精华及架构部分,逻辑清晰、内容完整,为快速形成售前方案提供参考。 感知基础设施 感知基础设施架构由感知范围、感知手…...

[赛博昆仑] 腾讯QQ_PC端,逻辑漏洞导致RCE漏洞
简介 !! 内容仅供学习,请不要进行非法网络活动,网络不是法外之地!! 赛博昆仑是国内一家较为知名的网络安全公司,该公司今日报告称 Windows 版腾讯 QQ 桌面客户端出现高危安全漏洞,据称“黑客利用难度极低、危害较大”,腾讯刚刚已经紧急发布…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...