SPRING是如何解决循环依赖的?为什么无法解决多例和构造器的循环依赖

2023-11-11

标签: java  spring

文章目录

1.什么是循环依赖

        所所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依 赖 A。它们之间的依赖关系如下:


循环依赖代码如下:

public class InstanceA  {
    @Autowired
    private InstanceB instanceB;  // InstanceA中依赖InstanceB
}
  • 1
  • 2
  • 3
  • 4
public class InstanceB  {
    @Autowired
    private InstanceA instanceA; // InstanceB中依赖InstanceA
}
  • 1
  • 2
  • 3
  • 4

无论先创建InstanceA还是InstanceB时,都会发生循环依赖!

2.解决循环依赖思路

        为了更加理解循环依赖的解决思路,尝试通过手写伪代码代码来实现Bean的初始化过程,在这之前先分析一下一、二、三级缓存 存在的意义,以及解决了什么问题吧!

只使用一级缓存时:
先看一下流程图:中间的闭环就是循环依赖


        为了解决循环依赖导致的闭环问题,我们可以在闭环中增加一个出口,具体做法是:修改把bean放入一级缓存的时机,以前是属性赋值、初始化完成后才放进去,现修改为实例化完成后就先加入缓存中。并在获取某个对象时先去一级缓存中找一下,找到了直接返回,这样可以解决单线程的缓存依赖!如图所示

        但是这里又会带来一个新的问题,就是在多线程模式下,如果别的线程从一级缓存中获取到的是实例化后的类,这样明显是不可行的,因为这个类并不完整,是纯净的Bean,属性并没有真正被赋值!为了解决这个问题,二级缓存应运而生。

为什么要使用二级缓存?

        二级缓存作用是:暴露早期对象,为了将成熟bean 和 纯净bean 分离。防止多线程中在Bean还未创建完成时读取到的Bean是不完整的。
        还有一点关于bean的Aop动态代理的问题,我们都知道Bean的aop动态代理创建是在初始化之后,但是循环依赖的Bean如果使用了AOP。 那无法等到解决完循环依赖再创建动态代理, 因为这个时候已经注入属性。 所以如果循环依赖的Bean使用了aop. 需要提前创建aop。

3.二级缓存能否解决循环依赖,三级缓存存在的意义

首先说明一下一二三级缓存的意义:
三级缓存分别是:

  1. singletonObject:一级缓存,该缓存key = beanName, value = bean;这里的bean是已经创建完成的,该bean经历过实例化->属性填充->初始化以及各类的后置处理。因此,一旦需要获取bean时,我们第一时间就会寻找一级缓存

  2. earlySingletonObjects:二级缓存,该缓存key = beanName, value = bean;这里跟一级缓存的区别在于,该缓存所获取到的bean是提前曝光出来的,是还没创建完成的。也就是说获取到的bean只能确保已经进行了实例化,但是属性填充跟初始化肯定还没有做完,因此该bean还没创建完成,仅仅能作为指针提前曝光,被其他bean所引用

  3. singletonFactories:三级缓存,不是用来存bean的实例,而是用来存函数接口、钩子函数的!该缓存key = beanName, value =beanFactory;在bean实例化完之后,属性填充以及初始化之前,如果允许提前曝光,spring会将实例化后的bean提前曝光,也就是把该bean转换成beanFactory并加入到三级缓存。在需要引用提前曝光对象时再通过singletonFactory.getObject()获取。

这里抛出问题,如果我们直接将提前曝光的对象放到二级缓存earlySingletonObjects,Spring循环依赖时直接取就可以解决循环依赖了,为什么还要三级缓存singletonFactory然后再通过getObject()来获取呢?这不是多此一举?

我们回到添加三级缓存,添加SingletonFactory的地方,看看getObject()到底做了什么操作

    this.addSingletonFactory(beanName, () -> {
        return this.getEarlyBeanReference(beanName, mbd, bean);
    });
  • 1
  • 2
  • 3

可以看到在返回getObject()时,多做了一步getEarlyBeanReference操作,这步操作是BeanPostProcess的一种,也就是给子类重写的一个后处理器,目的是用于被提前引用时进行拓展。即:曝光的时候并不调用该后置处理器,只有曝光,且被提前引用的时候才调用,确保了被提前引用这个时机触发。

4.多例和构造器为什么无法解决循环依赖

为什么多例Bean不能解决循环依赖?

        我们自己手写了解决循环依赖的代码,可以看到,核心是利用一个map,来解决这个问题的,这个map就相当于缓存。
为什么可以这么做,因为我们的bean是单例的,而且是字段注入(setter注入)的,单例意味着只需要创建一次对象,后面就可以从缓存中取出来,字段注入,意味着我们无需调用构造方法进行注入。
如果是原型bean,那么就意味着每次都要去创建对象,无法利用缓存;
如果是构造方法注入,那么就意味着需要调用构造方法注入,也无法利用缓存。

为什么Spring不能解决构造器的循环依赖?

         因为构造器是在实例化时调用的,此时bean还没有实例化完成,如果此时出现了循环依赖,一二三级缓存并没有Bean实例的任何相关信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。

5.如何进行扩展

6. SPRING在创建BEAN的时候,在哪里创建的动态代理?

①:如果没有循环依赖的话,在bean初始化完成后创建动态代理
②:如果有循环依赖,在bean实例化之后创建!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SPRING是如何解决循环依赖的?为什么无法解决多例和构造器的循环依赖 的相关文章

随机推荐

  • Ubuntu虚拟机和Windows实现文件拖拽复制粘贴

    方法 安装vm tools 1 在Ubuntu内部鼠标右键打开终端 2 更新apt get 一般新装的系统都需要更新apt get sudo apt get update ps 若无法更新 可以试着换一下镜像站 Ubuntu系统配置镜像站
  • Python基础系列2——Numpy数值计算及分析

    文章目录 1 实验内容 2 实验过程 2 1 numpy数组的建立 索引 计算 统计等 2 2 利用numpy对数据集 iris data 进行分析 3 实验结论及注意事项 1 实验内容 1 numpy数组的建立 索引 计算 统计等 2 利
  • BIOS和BootLoader uboot

    BIOS BIOS是英文 Basic Input Output System 的缩略语 直译过来后中文名称就是 基本输入输出系统 其实 它是一组固化到计算机内主板上一个ROM芯片上的程序 它保存着计算机最重要的基本输入输出的程序 系统设置信
  • Java Sort方法

    Java的sort方法就是排序 而且排的是升序 你要想降序可以先获得升序的 然后倒过来或者你重新写比较器Comparator的接口就可以 一 sort 排序方法本身 这里讲的sort方法 都是以Arrays类里面的方法为准 因为很多类的so
  • STM32 端口复用学习

    一 STM32端口复用 1 端口复用定义 STM32有很多的内置外设 这些外设的外部引脚都是与GPIO复用 也就是说 一个GPIO如果可以复用为内置外设的功能引脚 那么当这个GPIO作为内置外设使用的时候 就叫做复用 2 作用 最大限度的利
  • vue 实现计时器组件

    vue 实现计时器组件 结果图 v if 和 v show 的区别 总结来说v if是在不断的销毁和重建 v show 只是改变 display 属性 元素依然存在 dom 中 v if 切换开销大 v show 初始化开销大 time v
  • 人称代词用法大全

    语言发明出来自然是要给人用的 所以跟人相关的词就特别多 划分的很细 我们提到某个具体的人一般就直接说名字 但有时是泛指 或者前面已经提过名字了 后面用个啥简称指代下就清楚了 这就需要代词 代词嘛顾名思义是一个代称 是指代某个人或者某类人 某
  • 搭建使用 VS 开发 Qt 项目的环境

    搭建使用 VS 开发 Qt 项目的环境 个人认为 使用 Qt 工具开发 Qt 项目是最好的方案 在开发的过程出现的 bug 会比较少一些 但是有些同伴可能对 VS 比较钟爱 而 VS 又有此功能 因此想采用 VS 进行开发 本文将本人搭建成
  • nacos源码启动找不到istio包

    现象 源码版本2 1 0 启动时 编译不通过 报错 找不到 istio mcp v1alpha1 MetadataOuterClass Metadata istio networking v1alpha3 ServiceEntryOuter
  • 算法:链表

    单链表 单链表是一种链式存取的数据结构 链表中的数据是以结点来表示的 每个结点存储两个数据 一是该结点本身的值 二是其指向的下一结点的下标 用e i 表示节点i的值 用ne i 表示结点i指向的下一结点的坐标 head表示头结点的下标 id
  • 网关系统架构

    目录 一 API网关业务域 1 业务域 2 统一接入 3 安全防护 4 流量管控 5 协议转换 6 其他业务 1 接口文档管理 2 调试工具和示例 3 SDK自动生成能力 4 API增强 二 API网关核心指标 1 模型 2 安全性 3 高
  • C语言编写的简单计算器程序

    这两天在看一个C语言写的计算器程序 做了不少的功夫 跟着作者一步步的进行完善 了解了许多细节性的东西 在此自己做个总结 加深自己对程序的印象 也算是梳理 在该计算器程序 能进行加减乘除 sin cos exp等操作 同时能进行数值保存功能
  • 2021浙江工商计算机机试1

    1不在数列中的数字 给出一个长度为n的数列 包含1到n的数字 输出1到n中不在数列中的数字 include
  • Android studio64新建APP项目时,报错 junit:junit:4.12

    大家都是要求注释掉 但不想这样 看了很多博客 快绝望的时候 用这个办法成功了 文件 E android app 工程文件目录 下面的 build gradle 文件 在这个文件中加入最后划线3行 保存即可 然后再retry apply pl
  • VS安装配置OpenCV(C++)

    目录 第一章 Opencv安装及其环境变量配置 1 1下载并安装OpenCV 1 2 OpenCV环境变量配置 第二章 Visual Studio 2019 编译器下载安装 第三章 OpenCV开发环境配置 C 3 1创建项目 3 2 添加
  • 全国信息技术标准化技术委员会汉字内码扩展规范(GBK)

    全国信息技术标准化技术委员会 汉字内码扩展规范 GBK Chinese Internal Code Specification 1 0 版 按编码顺序排列 81 丂 丄 丅 丆 丏 丒 丗 丟 丠 両 丣 並 丩 丮 丯 丱 丳 丵 丷 丼
  • 记一次使用EasyExcel出现Convert excel format exception.You can try specifying the ‘excelType‘ yourself

    EasyExcel 3 0 出现 com alibaba excel exception ExcelCommonException Convert excel format exception You can try specifying
  • Vue常用知识点汇总

    1 Vue常见的指令有哪些 有什么用 1 v text 会替换掉元素里的内容 2 v html 可以渲染html界面 3 v clock 防止界面闪烁 4 v bind 界面元素属性值的绑定 简写为 5 v on 事件绑定 简写为 6 v
  • K8s 管理工具 kubectl 详解

    文章目录 一 陈述式管理 1 陈述式资源管理方法 2 k8s 相关信息查看 2 1 查看版本信息 2 2 查看资源对象简写 2 3 查看集群信息 2 4 配置kubectl自动补全 2 5 查看日志 2 6 基本信息查看 2 6 1 查看m
  • SPRING是如何解决循环依赖的?为什么无法解决多例和构造器的循环依赖

    标签 java spring 文章目录 1 什么是循环依赖 2 解决循环依赖思路 3 二级缓存能否解决循环依赖 三级缓存存在的意义 4 多例和构造器为什么无法解决循环依赖 5 如何进行扩展 6 spring在创建bean的时候 在哪里创建的