美文网首页
Spring 循环依赖

Spring 循环依赖

作者: Djbfifjd | 来源:发表于2020-09-15 22:43 被阅读0次

    一、什么是循环依赖(Spring circular dependency)?

    循环依赖其实就是循环引用,形成闭环。比如,A依赖B,B又依赖A,它们之间形成了循环依赖;或者A依赖B,B依赖C,C又依赖A,形成了循环依赖;更或者是自己依赖自己。如图:

    这不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。Spring 循环依赖场景有:
    ①构造器的循环依赖
    ②构造器依赖 + field 依赖或者 setter 依赖(A的构造器依赖B,B的 field 或者 setter 依赖A)
    ③field 依赖或者 setter 依赖

    具体情况如下:
    1️⃣A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象。(无法解决)
    2️⃣A的构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象,以及反之。(可以解决)
    3️⃣A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象,以及反之。(可以解决)

    二、怎么检测是否存在循环依赖

    检测循环依赖相对比较容易,Bean 在创建的时候可以给该 Bean 打标,如果递归调用回来发现正在创建中的话,说明循环依赖了。

    三、Spring是如果解决循环依赖的?

    Spring 通过三级缓存加上“提前曝光”机制,配合 Java 的对象引用原理,比较完美地解决了某些情况下的循环依赖问题!

    1. A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程。
    2. B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象
    3. B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化。

    1️⃣构造器参数循环依赖
    表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出 BeanCurrentlyIn CreationException 表示循环依赖。

    Spring 容器会将每一个正在创建的 Bean 标识符放在一个“当前创建 Bean 池”中,Bean 标识符在创建过程中将一直保持在这个池中,因此如果在创建 Bean 过程中发现自己已经在“当前创建Bean池”里时将抛出 BeanCurrentlyInCreationException 表示循环依赖;而对于创建完毕的 Bean 将从“当前创建Bean池”中清除掉。

    执行结果报错信息为:
    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
    Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

    2️⃣setter方式单例,默认方式

    如果要说 setter 方式注入的话,先看一张 Spring 中 Bean 实例化的图

    为什么用set方式就不报错了呢 ?
    结合上面那张图看,Spring 先是用构造实例化 Bean 对象 ,此时 Spring 会将这个实例化结束的对象放到一个 Map 中,并且 Spring 提供了获取这个未设置属性的实例化对象引用的方法。

    下面是Spring源码中的实现方法,。以下的源码在Spring的Bean包中的DefaultSingletonBeanRegistry.java类中

    3️⃣setter方式原型,prototype

    对于"prototype"作用域 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存"prototype"作用域的 bean,因此无法提前暴露一个创建中的 bean。

    scope="prototype" 意思是每次请求都会创建一个实例对象。两者的区别是:有状态的 bean 都使用 Prototype 作用域,无状态的一般都使用 singleton 单例作用域。

    打印结果:
    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
    Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

    为什么原型模式就报错了呢 ?
    对于“prototype”作用域 Bean,Spring 容器无法完成依赖注入,因为“prototype”作用域的 Bean,Spring 容器不进行缓存,因此无法提前暴露一个创建中的 Bean。

    相关文章

      网友评论

          本文标题:Spring 循环依赖

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