• Spring 是怎么解决循环依赖问题的呢?


    示例代码

    有两个业务代码分别是 A、B,并且还存在在相互引用,这样就形成了经典的循环依赖,而且这种循环依赖 Spring 不会出现问题,那么 Spring 是怎么解决的呢?让我们追溯下源码(目前分析的是单实例场景)

    示例代码如下:

    @Service
    publci class A {
    	@Autowired private B b;
    }
    
    @Service
    publci class B {
    	@Autowired private A a;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    getBean() 流程

    要分析怎么解决循环的问题,就得从 Spring 调用开始 getBean() 方法实例化 bean 的流程开始了,在 getBean() 方法中有四个非常重要的步骤如下:

    • 首先从123级缓存中查找是否实例化过了 bean
    • 调用构造方法实例化 bean
    • 将实例化 bean 保存到三级缓存
    • 属性赋值又会触发属性调用 getBean() 方法(有一种递归的感觉在找突破口退出)

    假设第一个调用 getBean() 方法的是 A ,它必定要经过上面四个步骤。

    A 首先过去查看123级缓存中是否有值?答案肯定是没有的,因为 A 还没有实例化过,正准备去实例化的路上呢。查找123级缓存的源码如下:

    在这里插入图片描述

    A 从缓存中没有找到,A 就会去调用 createBeanInstance() 方法创建 bean 对象(此时是 A 的半成品原始 bean,因为还没有属性赋值),源码如下:

    在这里插入图片描述

    此时 A 的半成品的原始 bean 已经创建好了,就要开始将实例化的 bean 保存到三级缓存中,源码如下:

    在这里插入图片描述

    此时这里有可能保存的是一个 Proxy、Cglib 代理对象,因为你要注意到,这里 key 对应的 value 值可不是一个简单的 bean,而是一个可以扩展的方法体,这里可以由你自己或者 Spirng 自己返回很多乱七八糟的东西,源码如下:

    在这里插入图片描述

    更多代理这方面的东西这里不展开细说,咱这里主要讲下单例 bean 的循环依赖 Spring 是怎么解决的。想要看可以参考我另一篇文章有说到。

    回到正题来,这里已经把 A 的半成品原始 bean 存放到了三级缓存中,然后呢要干什么?是不是得要去给 A 的 bean 填充属性,源码如下:

    在这里插入图片描述

    注意此时 @Autowired 注入的是 B,那么就是要给 B 进行属性填充,然后 B 又会触发 getBean() 操作,此时注意哦,是 B 过来执行 getBean() 的流程,A 现在卡在了给 B 属性赋值的地方,等给属性 B 赋值完之后还有回来这个地方继续往下执行代码。

    那么 B 又会经历过前面提到过的 getBean() 的四个核心步骤:

    B 又过去查看123级缓存中是否有值?答案肯定是没有的,因为 B 还没有实例化过,现在正准备去实例化的路上呢。查找123级缓存的源码如下所示:

    在这里插入图片描述

    B 从缓存中也没有找到数据,B 也会去调用 createBeanInstance() 方法创建 bean 对象(此时是 B 的半成品原始 bean,因为还没有属性赋值),源码如下:

    在这里插入图片描述

    此时 B 的半成品的原始 bean 已经创建好了,就要开始将实例化的 bean 保存到三级缓存中,源码如下:

    在这里插入图片描述

    这里已经把 B 的半成品原始 bean 也存放到了三级缓存中,然后呢要干什么?是不是得要去给 B 的 bean 填充属性,源码如下:

    在这里插入图片描述

    注意此时 @Autowired 注入的是 A,那么就是要给 A 进行属性填充,然后 A 又会触发 getBean() 操作,此时注意哦,是 A 过来执行 getBean() 的流程,B 现在卡在了给 A 属性赋值的地方,等给属性 A 赋值完之后还有回来这个地方继续往下执行代码。

    有没有注意到此时现在 Sping 的缓存中已经保留了两个半成品的原始 bean 对象,就是 A、B,都是保存在了三级缓存中

    而且 A 第一次已经调用过一次 getBean() 方法了,第一次的调用还卡在给 B 属性赋值的地方,然后现在 B 过来触发了 A 的属性赋值,A 开始第二次调用 getBean() 方法了

    那么 A 又要开始执行 getBean() 的四大核心步骤:

    记住现在 Spring 缓存中已经有两个半成品 A、B,都保存着三级缓存中呢

    A 又开始从123级缓存中查看是否有数据,此时过来发现三级缓存中有值了(是 A 在第一次调用 getBean() 的时候放进去的),源码如下:

    在这里插入图片描述

    A 就从三级缓存中取出数据,然后还会把取出的额数据移到二级缓存 earlySingletonObjects 中保存,这个二级缓存说实话,如果不存在三者之间的循环依赖问题,基本没啥用,多此一举。这种两个之间的循环依赖完全用一个三级缓存就可以解决。

    A 过来做完数据转移,就拿着取出的 bean 对象开始返回,注意此时应该返回到哪里呢?

    你要知道这个 A 是因为 B 在给属性 A 赋值时触发的 A 调用 getBean() 方法的,此时属性 A 调用 getBean() 拿到了值,这也就表示 B 给属性 A 赋值已经赋值好了(底层都是通过反射赋值的)。

    B 给属性 A 赋值完成了,那么表示 B 的整个 getBean() 就结束了,此时应该返回到哪里呢?

    B 是怎么过来的?是不是因为在 A 调用 getBean() 流程中,A 要给属性 B 赋值操作,然后出发的 B 调用 getBean() ,现在 B 调用 getBean() 流程已经结束,是不是就会返回 A 给属性 B 赋值的地方,源码如下:

    在这里插入图片描述

    A 给属性 B 赋值完之后,A 继续往下执行,最终会把完成实例化的 A bean 存放到一级缓存,并且清空23级缓存和一个标记缓存,源码如下:

    在这里插入图片描述

    至此 A 里面有了 B,B 里面有了 A 的对象引用,这就是 Spring 怎么解决单实例的循环依赖问题,核心是通过缓存先保存自己的一份引用。

    至于多例模式下的循环依赖,Spring 是不支持的,因为多例情况下不会走缓存,并且是以懒加载的形式创建 bean 实例的。

  • 相关阅读:
    知识点记录:李群李代数,微分流形,微分几何,图论
    abc324 e
    微软hotmail邮箱的存储空间查询
    零代码编程:用ChatGPT批量删除文件名称中的部分内容
    win10+GTX1050+pytorch安装
    Snipaste 提高十倍生产力工作效率,堪称最强神器
    动态规划(子序列问题)
    Python绘图-14绘制3D图(下)
    games101作业七,计算机图形学作业三,详细知识点总结(附代码)
    http1和http2的主要区别
  • 原文地址:https://blog.csdn.net/qq_35971258/article/details/126482491