解析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 ...
随机推荐
- [转帖]Linux系统中的Page cache和Buffer cache
Free命令显示内存 首先,我们来了解下内存的使用情况: Mem:表示物理内存统计 total:表示物理内存总量(total = used + free) used:表示总计分配给缓存(包含buffe ...
- [转帖]龙芯 vs 飞腾:各种测试数据看国产CPU水平
https://zhuanlan.zhihu.com/p/99921594 2019年年末,龙芯.飞腾两大国产CPU巨头更是相继组织了规模宏大的年会,发布了新型桌面芯片及其整机产品,顿时硝烟四起.各大 ...
- 跨主机Docker容器通信的学习
背景 骨折在家找自己的人比较少. 又因为出不去也没法做运动,就不如将之前没学习深入的地方学习下 先是进行Docker 搭建 redis cluster的处理. 当时发现必须使用 --net=host进 ...
- Bitmap、RoaringBitmap原理分析
作者:京东科技 曹留界 在人群本地化实践中我们介绍了人群ID中所有的pin的偏移量可以通过Bitmap存储,而Bitmap所占用的空间大小只与偏移量的最大值有关系.假如现在要向Bitmap内存入两个p ...
- Docker搭建SvnServer
下载svn-server官方镜像 docker pull garethflowers/svn-server 运行svn-server容器 docker run -v /home/svn:/var/op ...
- 【Jmeter】按比例分配Api压测
先看 [Jmeter]基础介绍-详细 [Jmeter]Request1输出作为Request2输入-后置处理器 继续聊提出的第二个问题,即 2.需要按比例分配API请求并发,以模拟真实的API压力场景 ...
- 从嘉手札<09-06-2023>
时常会想 这个世界什么是长久的 我们走在时代的映照下,行色匆匆. 因为别人的悲欢而悲欢,因为自己的局限而挣扎. 晨而得志,暮而踌躇. 青楼梦好,难赋深情. 这个世界有很多的选择. 金钱,酒色,健康,相 ...
- ROS节点通信(二)service和client
目录 1.说明 2.创建功能包 3.自定义通信数据类型 4.编写代码 5.编译配置 5.1.CMakeLists.txt 5.2.package.xml 6.编译运行 1.说明 ROS的节点通信模式有 ...
- Dart常用核心知识
Dart简述 Dart 是一个为全平台构建快速应用的客户端优化的编程语言,免费且开源. Dart是面向对象的.类定义的.单继承的语言.它的语法涵盖了多种语言的语法特性,如C,JavaScirpt, J ...
- JWT( JSON Web Token —— JSON Web 令牌 )的学习笔记
一.跨域认证的问题 互联网服务离不开用户认证.一般流程是下面这样: 1.用户向服务器发送用户名和密码. 2.服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色.登录时间等等. ...