专栏首页开发技术再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的?

再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的?

开心一刻

  一天,侄子和我哥聊天,我坐在旁边听着

  侄子:爸爸,你爱我妈妈吗?

  哥:这话说的,不爱能有你吗?

  侄子:确定有我不是因为荷尔蒙吗?

  哥:因为什么荷尔蒙,因为爱情!

  侄子:那我妈花点钱,你咋老说呢?

  哥:这你就不懂了,挣钱本不易,花钱要仔细

  侄子:快得了吧,挣钱这么少,我妈都没跑,给你照顾家,钱还不让花

  哥:我发现你这孩子怎么不知道好赖呢,我攒钱不是为了给你去媳妇啊

  侄子:那你赶紧给我妈花吧,我妈要是跑了,你还得花钱娶一个,到最后,钱我捞不着,亲妈还混没了

  我:通透!!!

写在前面

  Spring 中常见的循环依赖有 3 种:单例 setter 循环依赖、单例构造方法循环依赖、原型循环依赖

  关于单例 setter 循环依赖,Spring 是如何甄别和处理的,可查看:Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗

  单例构造方法循环依赖

  何谓单例构造方法循环依赖了,我们看具体代码就明白了

  两个要素:① scope 是默认值,也就是 singleton;② 多个实例之间通过构造方法形成了循环依赖

  这种情况下,Spring 是怎么处理的了,我们先来看看执行结果

Spring 启动过程中报错了: Error creating bean with name 'cat': Requested bean is currently in creation: Is there an unresolvable circular reference?

  问题就来了:Spring 是如何甄别单例情况下的构造方法循环依赖的,然后进行报错的

  大家先把这个问题暂留在心里,我们再来看看什么是原型循环依赖

  原型循环依赖

  同样,我们直接看代码就明白何谓原型循环依赖了

  同样是 2 个要素:① scope 不是默认值,而是 prototype,也就是原型,每次获取该实例的时候都会新建;② setter 循环依赖

  这种情况下 Spring 又会有什么样的执行结果了

Spring 启动正常,但从 Spring 容器获取 loop 实例的时候,报了同样的错误

  问题来了:① Spring 是如何甄别原型循环依赖的,然后进行报错提示的

       ② 为什么两种情况的报错时机会不一致,一个在 Spring 启动过程中,一个却在使用 Spring 的过程中

  示例代码地址:spring-circle-dependence-type

  上面的 3 个问题,概括下就是

    1、Spring 是如何甄别单例情况下的构造方法循环依赖的

    2、Spring 是如何甄别原型循环依赖的

    3、为什么单例构造方法循环依赖和原型循环依赖的报错时机不一致

  我们慢慢往下看,跟源码的过程可能比较快,大家看仔细了

  还是那句话

  看完之后仍有疑问,可以评论区留言,也可以自行去查阅相关资料进行解疑

  源码起点

    Spring 读取和解析 xml 的过程,我们就不去跟了,我们重点跟一下我们关注的内容

    我们从 DefaultListableBeanFactory 类的 preInstantiateSingletons 方法作为起点

    按如下顺序可以快速的找到起点,后面两种情况都从此处开始进行源码跟踪

构造方法循环依赖的甄别

  闲话少说,我们直接开始跟源码

  获取 cat 实例

cat 的 RootBeanDefinition 中有几个属性值得我们注意下

  接着往下走

  我们来到了 createBeanInstance 方法,此时 Set<String> singletonsCurrentlyInCreation 只存放了 cat

singletonsCurrentlyInCreation 看字面意思就知道,存放的是当前正在创建中的单例对象名

  我们接着往下跟

  由于 constructorArgumentValues 中有元素,所以需要通过有参构造函数来创建 cat 对象

  因为构造函数的参数是 Dog 类型的 dog ,所以通过反射调用 Cat 的有参构造函数来创建 cat 之前,需要先从 Spring 容器中获取到 dog 对象

  获取 Cat 构造函数依赖的 dog 实例

  所以流程又来到了我们熟悉的 getBean ,只是现在获取的是 dog ;获取流程与获取 cat 时一样,所以跟的速度会快一些,大家注意看我停顿的地方

  此时 singletonsCurrentlyInCreation 存放了 cat 和 dog ,表示他们都在创建中

  又来到了 createBeanInstance ,过程与之前 cat 的过程一样,我们接着往下看

  又来到了熟悉的 getBean ,需要从 Spring 容器获取 Dog 构造函数依赖的 cat 对象

  获取 Dog 构造函数依赖的 cat 对象

  接下来重点来了,大家看清楚了

  因为 singletonsCurrentlyInCreation 已经存在 cat 了, !this.singletonsCurrentlyInCreation.add(beanName) 结果就是 true

  说明陷入死循环了,所以抛出了 BeanCurrentlyInCreationException

  我们在控制台看到的异常信息就从这来的

原型循环依赖的甄别

  原型类型的实例有个特点:每次获取都会重新创建一个实例,那在 Spring 启动过程中,还有创建的必要吗?

  Spring 启动不创建 prototype 类型的实例

  我们来跟下源码就明白了

  关键代码

  不符合上述 3 个条件的实例,在 Spring 启动过程中都不会被创建

  下面接着讲正题,来看看 Spring 是如何甄别原型循环依赖的

  获取 loop 实例

  在 loop 实例创建之前,调用了 beforePrototypeCreation 方法,将 loop 名放到了 ThreadLocal<Object> prototypesCurrentlyInCreation

  表示当前线程正在创建 loop ,我们接着往下看

  原型类型的对象创建过程分两步:① 实例化(反射调构造方法),② 初始化(属性填充),和单例类型对象的创建过程是一样的

  依赖的处理是在初始化过程中进行的, loop 对象依赖 circle 属性,所以对 loop 对象的 circle 属性进行填充的时候,需要去 Spring 容器获取 circle 实例

  又来到了我们熟悉的 getBean ,获取 loop 依赖的 circle 实例,我们继续往下跟

  在 circle 对象创建之前,同样调用了 beforePrototypeCreation 方法,那么此时 prototypesCurrentlyInCreation 中就同时存在 loop 和 circle

  表示当前线程正在创建 loop 实例和 circle 实例;继续往下走

  兜兜转转又来到了 getBean ,获取 circle 对象依赖的 loop 属性,接下来是重点,大家看仔细了

  因为 prototypesCurrentlyInCreation 中存在 loop 了,说明当前线程正在创建 loop 实例

  而现在又要创建新的 loop ,说明陷入死循环了,所以抛出了 BeanCurrentlyInCreationException

总结

  经过上面的梳理,相信大家对之前的三个问题都没有疑问了,我们来总结下

  1、Spring 是如何甄别单例情况下的构造方法循环依赖的

    Spring 通过 Set<String> singletonsCurrentlyInCreation 记录当前正在创建中的实例名称

    创建实例对象之前,会判断 singletonsCurrentlyInCreation 中是否存在该实例的名称,如果存在则表示死循环了,那么抛出 BeanCurrentlyInCreationException

  2、Spring 是如何甄别原型循环依赖的

    Spring 通过 ThreadLocal<Object> prototypesCurrentlyInCreation 记录当前线程正在创建中的原型实例名称

    创建原型实例之前,会判断 prototypesCurrentlyInCreation 中是否存在该实例的名称,如果存在则表示死循环了,那么抛出 BeanCurrentlyInCreationException

  3、为什么单例构造方法循环依赖和原型循环依赖的报错时机不一致

    单例构造方法实例的创建是在 Spring 启动过程中完成的,而原型实例是在获取的时候创建的

    所以两者的循环依赖的报错时机不一致

参考

Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring 是如何解决循环依赖的?

    Requested bean is currently in creation: Is there an unresolvable circular refer...

    程序员小航
  • Spring是如何解决循环依赖的

    在面试的时候这两年有一个非常高频的关于spring的问题,那就是spring是如何解决循环依赖的。这个问题听着就是轻描淡写的一句话,其实考察的内容还是非常多的,...

    纪莫
  • Spring 是如何解决循环依赖的?

    正要创建的 bean 记录在缓存中,Spring 容器架构一个正在创建的 bean 标识符放在一个 “当前创建 bean 池”中国, 因此如果在创建 Bean ...

    王小明_HIT
  • Spring 中循环依赖是如何解决?

    支持不支持循环引用this.allowCircularReferences这个参数,可以在之前将参数修改为false;

    黑白格
  • spring:我是如何解决循环依赖的?

    最近项目组的一个同事遇到了一个问题,问我的意见,一下子引起的我的兴趣,因为这个问题我也是第一次遇到。平时自认为对spring循环依赖问题还是比较了解的,直到遇到...

    苏三说技术
  • 再谈spring的循环依赖是怎么造成的?

    老生常谈,循环依赖!顾名思义嘛,就是你依赖我,我依赖你,然后就造成了循环依赖了!由于A中注入B,B中注入A导致的吗?

    烂猪皮
  • 烂大街的Spring循环依赖该如何回答?

    从字面上来理解就是A依赖B的同时B也依赖了A,就像上面这样,或者C依赖与自己本身。体现到代码层次就是这个样子

    sowhat1412
  • Spring IOC 容器源码分析:循环依赖的解决方法

    本文,我们来看一下 Spring 是如何解决循环依赖问题的。在本篇文章中,我会首先向大家介绍一下什么是循环依赖。然后,进入源码分析阶段。为了更好的说明 Spri...

    CG国斌
  • 跳出源码地狱,Spring巧用三级缓存解决循环依赖-原理篇

    在探讨Spring三级缓存解决循环引用之前,我们需要了解一点就是Spring所谓的循环依赖到底是什么,是如何产生的,为什么会产生这种问题?

    止术
  • 循环依赖 之 手写代码模拟spring循环依赖

    如上图: A依赖了B, B同时依赖了A和C , C依赖了A. 这也是循环依赖. , 形成了一个闭环

    用户5546570
  • 【Spring】如何解决Spring的循环依赖问题?这道面试题助我拿到了Offer!!

    作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了...

    冰河
  • 面试问你Spring如何解决循环依赖的时候,不要一脸懵逼了

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的。这个问题算是关于Spring的一个高频面试题,因为如果不刻意研...

    IT大咖说
  • Spring IOC原理补充(循环依赖、Bean作用域等)

    通过之前的几篇文章将Spring基于XML配置的IOC原理分析完成,但其中还有一些比较重要的细节没有分析总结,比如循环依赖的解决、作用域的实现原理、BeanPo...

    夜勿语
  • 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖

    如上图: A依赖了B, B同时依赖了A和C , C依赖了A. 这也是循环依赖. , 形成了一个闭环

    用户7798898
  • Spring中解决循环依赖报错的问题

    当一个ClassA依赖于ClassB,然后ClassB又反过来依赖ClassA,这就形成了一个循环依赖:

    Happyjava
  • Spring5.0源码深度解析之Spring是如何利用三级缓存解决循环依赖的问题

    Spring已经成为了开发项目的不可缺少的组件了,我们在平常开发项目中难免会遇到以下这些情况,比如说,我有A类和B类,两个业务类都注入到Spring容器里了,且...

    黎明大大
  • 这个Spring循环依赖的坑,90%以上的人都不知道

    这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题。这...

    Bug开发工程师
  • Java面试:2021.05.28

    大体来说,经历以下过程:接口需求调研、接口测试工具选择、接口测试用例编写、接口测试执行、接口测试回归、接口测试自动化持续集成。具体来说,接口测试流程分成以下九步...

    夕梦
  • 一个非典型Spring循环依赖的问题分析

    这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题。这...

    猿天地

扫码关注云+社区

领取腾讯云代金券