美文网首页Java
Spring 是如何判定原型循环依赖和构造方法循环依赖的?

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

作者: 废柴程序员 | 来源:发表于2021-10-08 16:22 被阅读0次

    写在前面

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

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

    单例构造方法循环依赖

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

    image.png

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

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

    image.png

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

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

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

    原型循环依赖

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

    image.png

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

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


    image.png

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

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

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

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

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

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

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

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

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

    还是那句话

    image.png

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

    源码起点

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

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

    image.png

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

    构造方法循环依赖的甄别

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

    获取 cat 实例

    image.png

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

    image.png

    接着往下走

    image.png

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

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

    我们接着往下跟

    image.png

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

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

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

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

    image.png

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

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

    image.png

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

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

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

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

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

    image.png

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

    原型循环依赖的甄别

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

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

    我们来跟下源码就明白了

    image.png

    关键代码

    image.png

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

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

    获取 loop 实例

    image.png

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

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

    image.png

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

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

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

    image.png

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

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

    image.png

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

    image.png

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

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

    总结

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

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

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

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

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

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

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

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

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

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

    相关文章

      网友评论

        本文标题:Spring 是如何判定原型循环依赖和构造方法循环依赖的?

        本文链接:https://www.haomeiwen.com/subject/gsuxoltx.html