zoukankan      html  css  js  c++  java
  • 再探循环依赖 → 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 的循环依赖,源码详细分析 → 真的非要三级缓存吗

  • 相关阅读:
    JSONP
    懒加载
    HTTP 状态代码
    java4中创建内对象的方法
    注册jdbc驱动程序的三种方式
    java Clone()克隆
    Class.forName()的理解
    Bitmap介绍
    前端-PC端瀑布流【10张图】
    百度小程序-图片画廊-使用previewImage方法实现
  • 原文地址:https://www.cnblogs.com/youzhibing/p/14514823.html
Copyright © 2011-2022 走看看