解析Spring中的循环依赖问题:初探三级缓存
什么是循环依赖?
这个情况很简单,即A对象依赖B对象,同时B对象也依赖A对象,让我们来简单看一下。
// A依赖了B
class A{
public B b;
}
// B依赖了A
class B{
public A a;
}
这种循环依赖可能会引发问题吗?
在没有考虑Spring框架的情况下,循环依赖并不会带来问题,因为对象之间相互依赖是非常普遍且正常的现象。
比如
A a = new A();
B b = new B();
a.b = b;
b.a = a;
这样,A,B就依赖上了。
然而,在Spring框架中存在一个令人头疼的问题,即循环依赖,这一问题的根源是什么呢?
在Spring框架中,一个对象的实例化并非简单地通过new关键字完成,而是经历了一系列Bean生命周期的阶段。正是由于这种Bean的生命周期机制,才导致了循环依赖问题的出现。在Spring应用中,循环依赖问题是一个常见现象,有些情况下Spring框架能够自动解决这种问题,但在其他情况下,需要开发人员介入并进行手动解决。接下来将详细探讨这些情况。
要深入理解Spring中的循环依赖,首先需要对Spring中Bean的完整生命周期有所了解。在这里不会深入展开对Bean生命周期的详细描述,因为之前的文章已经单独探讨过这一话题。因此,这里将简要概述Bean生命周期的大致过程。
Spring 管理的对象称为 Bean,通过Spring的扫描机制获取到类的BeanDefinition后,接下来的流程是:
- 解析BeanDefinition以实例化Bean:
a. 推断类的构造方法。
b. 利用反射机制实例化对象(称为原始对象)。 - 填充原始对象的属性,实现依赖注入。
- 如果原始对象中的方法被AOP增强,生成代理对象:
a. 根据原始对象生成代理对象。 - 将生成的代理对象存放到单例池(在源码中称为singletonObjects)中,以便下次直接获取。
这个过程简要描述了Spring容器在实例化Bean并处理AOP时的流程。
在Spring中,Bean的生成过程涉及多个复杂步骤,远不止上述简要提及的7个步骤。除了所列步骤外,还包括诸如Aware回调、初始化等繁琐流程,这些内容涉及的细节繁多,不在本文讨论范围。
在创建B类的Bean时,如果B类包含一个名为a的A类属性,那么在生成B的Bean时,需要确保A类的Bean已经存在。然而,由于B类Bean的创建条件是A类Bean的依赖注入过程,因此可能会导致循环依赖问题。这意味着在容器尝试实例化B类Bean时,它必须首先解决A类Bean的依赖关系,而A类Bean的实例化又依赖于B类Bean。所以这里就出现了循环依赖:
ABean创建-->依赖了B属性-->触发BBean创建--->B依赖了A属性--->需要ABean(但ABean还在创建过程中)
因此,这一系列问题最终导致无法成功创建ABean,进而也无法顺利创建BBean。
在这种循环依赖的情况下,正如前文所述,Spring通过一些机制来协助开发者解决部分循环依赖问题,这便是三级缓存。
三级缓存
在此简要介绍三级缓存的概念,随后在探讨AOP对象如何解决循环依赖问题时,将会对其进行更为详尽的回顾。
- 在Spring框架中,单例对象缓存在
singletonObjects中,其中存储的是已经经历了完整生命周期的bean对象。 earlySingletonObjects中的“early”一词表明其中缓存的是早期的bean对象。这里的“早期”指的是Bean的生命周期尚未完成,但已经将该Bean放入了earlySingletonObjects中。singletonFactories中存储的是ObjectFactory,即对象工厂,用于创建早期bean对象的工厂。
在前文的分析中,我们得知产生循环依赖问题的主要原因是Bean之间相互依赖,导致在创建Bean时出现了循环引用的情况。主要是:
A创建时--->需要B---->B去创建--->需要A,从而产生了循环

因此,我们现在将深入探讨为何缓存机制能够成功解决这种循环依赖难题。那么,如何打破这一循环呢?我们可以引入一个中间层(缓存)来化解。

在A的Bean创建过程中,在执行依赖注入之前,首先将A的原始Bean提前放入缓存中,这样一来,其他Bean在需要时可以直接从缓存中获取,随后才进行依赖注入操作。
在这种情况下,当A的Bean依赖于B的Bean时,如果B的Bean尚不存在,则必须启动B的Bean创建流程。这一流程与A的Bean创建过程相似,首先生成B的原始对象,然后将其提前暴露并置入缓存中。
接着,在对B的原始对象执行依赖注入A操作时,此时可以从缓存中检索A的原始对象(尽管这仅为A的原始对象,尚非最终Bean状态)。当B的原始对象完成依赖注入后,B的生命周期随之终结,从而也促使A的生命周期得以顺利结束。
在整个流程中,只存在一个A原始对象,因此对于B而言,即使在属性注入阶段将A原始对象注入,也并不会有任何影响,因为A原始对象在随后的生命周期中保持不变,未在堆中发生任何变化。
总结
在文章中详细探讨了循环依赖问题及其解决思路分析,揭示了Spring所提供的Bean创建过程并非如我们所想象的那样简单。这一过程涉及众多复杂步骤,因此Spring引入了缓存机制,通过在后续阶段逐步维护堆中的初始对象,并逐步进行赋值来逐步完成Bean的创建。这种缓慢而谨慎的方式确保了Bean的正确创建。
然而,这种处理方式仅适用于普通对象的创建。我们了解到,Spring还涉及另一个重要特性,即面向切面编程(AOP)。根据这一逻辑,AOP代理对象可能会遇到一些问题,这将在接下来的章节中进行深入讨论。这也解释了为何Spring需要三级缓存而不仅仅是二级缓存的原因。
解析Spring中的循环依赖问题:初探三级缓存的更多相关文章
- 面试必杀技,讲一讲Spring中的循环依赖
本系列文章: 听说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configu ...
- 面试阿里,腾讯,字节跳动90%都会被问到的Spring中的循环依赖
前言 Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃 ...
- 从一部电影史上的趣事了解 Spring 中的循环依赖问题
title: 从一部电影史上的趣事了解 Spring 中的循环依赖问题 date: 2021-03-10 updated: 2021-03-10 categories: Spring tags: Sp ...
- 【Spring】Spring中的循环依赖及解决
什么是循环依赖? 就是A对象依赖了B对象,B对象依赖了A对象. 比如: // A依赖了B class A{ public B b; } // B依赖了A class B{ public A a; } ...
- Spring中的循环依赖解决详解
前言 说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚.本文就试着尽自己所能,对此做出一个较详细的解读.另,需注意一点,下文 ...
- 一起来踩踩 Spring 中这个循环依赖的坑
1. 前言 2. 典型场景 3. 什么是依赖 4. 什么是依赖调解 5. 为什么要依赖注入 6. Spring的依赖注入模型 7. 非典型问题 参考资料 1. 前言 这两天工作遇到了一个挺有意思的Sp ...
- Spring中的循环依赖
循环依赖 在使用Spring时,如果主要采用基于构造器的依赖注入方式,则可能会遇到循环依赖的情况,简而言之就是Bean A的构造器依赖于Bean B,Bean B的构造器又依赖于Bean A.在这种情 ...
- Spring中解决循环依赖报错的问题
什么是循环依赖 当一个ClassA依赖于ClassB,然后ClassB又反过来依赖ClassA,这就形成了一个循环依赖: ClassA -> ClassB -> ClassA 原创声明 本 ...
- 曹工说Spring Boot源码(29)-- Spring 解决循环依赖为什么使用三级缓存,而不是二级缓存
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 彻底理解Spring如何解决循环依赖
Spring bean生命周期 可以简化为以下5步. 1.构建BeanDefinition 2.实例化 Instantiation 3.属性赋值 Populate 4.初始化 Initializati ...
随机推荐
- [转帖]armv6、armv7、armv7s、armv8、armv64及其i386、x86_64区别
ARM处理器指令集 一. 苹果模拟器指令集: 指令集 分析 i386 针对intel通用微处理器32架构的 x86_64 针对x86架构的64位处理器 i386|x86_64 是Mac处理器的指令集, ...
- echarts控制柱状图柱条的宽度
barWidth属性 series: [{ name: '销量', type: 'bar', barWidth : 30,//柱图宽度 data: [5, 20, 36, 10, 10, 20] }]
- 在Unity中使用SQLite保存配置表数据(For Lua)
在Lua中使用sqlite Lua版本Sqlite文档:http://lua.sqlite.org/index.cgi/doc/tip/doc/lsqlite3.wiki sqlite官网:https ...
- 【主流技术】浅析 ElasticSearch7.x 的基本结构及应用(一)
目录 前言 一.概述 1.1基本认识 1.2核心概念 对比关系型数据库 1.3倒排索引 例一: 例二: 1.4了解ELK 二.安装(基于 CentOS) 2.1安装声明 2.2 使用 Docker 安 ...
- win10安装wget,从此可以更快的下载文件 and windows10 下 zip命令行参数详解
1.win10安装wget 1.1安装下载 GNU Wget 1.21.3 for Windows 依次如下: 2.将下载好的wget.exe放到 C:/windows/system32文件夹下 也可 ...
- 【2】Anaconda下:ipython文件的打开方式,Jupyter Notebook中运行.py文件,快速打开ipython文件的方法!
相关文章: [1]Anaconda安装超简洁教程,瞬间学会! [2]Anaconda下:ipython文件的打开方式,Jupyter Notebook中运行.py文件,快速打开ipython文件的方法 ...
- C/C++ 反汇编:函数与结构体
反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解.外挂技术.病毒分析.逆向工程.软件汉化等领域,学习和理解反汇编对软件调试.系统漏洞挖掘.内核原理及理解高级语言代码都有相当大的帮助, ...
- Mysql数据库迁移|如何把一台服务器的mysql数据库迁移到另一台服务器上的myql中
前言 那么这里博主先安利一下一些干货满满的专栏啦! Linux专栏https://blog.csdn.net/yu_cblog/category_11786077.html?spm=1001.2014 ...
- 关于行结束符(CR、LF)、回车、换行
CR(Carriage Return)表示回车 LF(Line Feed)表示换行 Dos和Windows采用回车+换行(CR+LF)表示下一行而UNIX/Linux采用换行符(LF)表示下一行苹果机 ...
- Python数组合并和数组分割(数组拆分)
数组的合并和拆分 ## 将数组按照固定长度进行拆分,返回一个二维数组 def list_split(source_list, n): return [source_list[i:i+n] for i ...