解析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后,接下来的流程是:

  1. 解析BeanDefinition以实例化Bean:
    a. 推断类的构造方法。
    b. 利用反射机制实例化对象(称为原始对象)。
  2. 填充原始对象的属性,实现依赖注入。
  3. 如果原始对象中的方法被AOP增强,生成代理对象:
    a. 根据原始对象生成代理对象。
  4. 将生成的代理对象存放到单例池(在源码中称为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,从而产生了循环

image

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

image

在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需要三级缓存而不仅仅是二级缓存的原因。

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

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

相关文章

Linux 操作系统多路IO复用

1.多路IO复用 多路I/O复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。 这个机制能够通过select/poll/eroll等来使用。这些函数都可以同时监视多…

ICode国际青少年编程竞赛- Python-3级训练场-能量状态判断1

ICode国际青少年编程竞赛- Python-3级训练场-能量状态判断1 1、 for i in range(6):Spaceship.step(2)if not Item[i].broken():Spaceship.turnLeft()Spaceship.step(4)Spaceship.turnLeft()Spaceship.turnLeft()Spaceship.step(4)Spaceship.turnLeft()2、 for i in range(6)…

每日一题——力扣27. 移除元素(举一反三)

题目链接:https://leetcode.cn/problems/remove-element/description/ 菜鸡写法: // 函数定义,移除数组nums中所有值为val的元素,并返回新的数组长度 int removeElement(int* nums, int numsSize, int val) {// 如果数组长度为…

聚观早报 | 苹果新款iPad Pro发布;国产特斯拉4月交付量

聚观早报每日整理最值得关注的行业重点事件,帮助大家及时了解最新行业动态,每日读报,就读聚观365资讯简报。 整理丨Cutie 5月9日消息 苹果新款iPad Pro发布 国产特斯拉4月交付量 iOS 18新功能爆料 真我GT Neo6续航细节 三星Galaxy Z F…

java图片水印字体乱码问题

问题描述:在linux Centos-7.5_64bit系统的其他服务器上不乱码,在部署项目的正式服务器乱码 水印字体设置是 微软雅黑 Font wordFont new Font("微软雅黑", Font.ITALIC,(srcImgHeightsrcImgWidth)/50); 一.Springboot项目,部署在…

文献速递:深度学习医学影像心脏疾病检测与诊断--基于迁移学习的生成对抗网络用于静态和动态心脏PET的衰减校正

Title 题目 Transfer learning‑based attenuation correction for static and dynamic cardiac PET using a generative adversarial network 基于迁移学习的生成对抗网络用于静态和动态心脏PET的衰减校正 01 文献速递介绍 心脏正电子发射断层扫描(PET&#xf…

Redis 支持的 Java 客户端都有哪些?

Redis 是一种高性能的键值存储系统,它以其快速、灵活和可扩展的特性而闻名。在 Java 开发中,与 Redis 交互的方式通常是通过使用 Redis 的 Java 客户端。 这些客户端提供了访问 Redis 数据库的接口,使开发人员能够在 Java 应用程序中轻松地使…

伦敦金交易常识 原来可以这样分类

如果投资者想做好伦敦金交易,对市场中的伦敦金交易常识等等都需要加以学习和研究,别小看那些伦敦金交易常识,很多高深的交易策略也是从常识出发慢慢建立起来的。伦敦金交易常识可以分为几类,下面我们就来讨论一下。 伦敦金市场的基…

Git 如何管理标签命令(tag)

1.查看本地仓库tag --1.查看本地仓库tag UserDESKTOP-2NRT2ST MINGW64 /e/GITROOT/STARiBOSS/STARiBOSS-5GCA (gw_frontend_master) $ git tag 1stBossUpgrade V10.0.1_20220224_test V10.0.1_20220301_test tag-gwfrontend-V1.0.12-230625 tag-gw_frontend-23.08.29 tag-gw_f…

Docker快速搭建NAS服务——FileBrowser

Docker快速搭建NAS服务——FileBrowser 文章目录 前言FileBrowser的搭建docker-compose文件编写运行及访问 总结 前言 本文主要讲解如何使用docker在本地快速搭建NAS服务,这里主要写如下两种: FileBrowser1:是一个开源的Web文件管理器&…

45.乐理基础-音符的组合方式-复附点

复附点: 复附点顾名思义就是两个附点 复附点表示的音符,有多少拍?下面拿 复附点四分音符举例,可以把整个音符看成三部分,第一部分是原本的四分音符,第二部分是第一个附点,第三部分是第二个附点&…

java注解全网最细

引言 在java编程中,注解(Annotation)是一种元数据,它提供了关于程序代码的额外信息。注解不直接影响程序的执行,但可以在运行时提供有关程序的信息,或者让编译器执行额外的检查。 下面笔者通过循序渐进的…