• 循环依赖


    定义

    什么是循环依赖?循环依赖就是循环引用,就是两个或者多个bean相互之间的持有对方,比如TestA引用TestB,TestB引用TestC,TestC引用TestA,这样它们最终反映为一个环。这里需要强调一点,此情形不是循环调用,循环调用是方法之间的环调用。循环引用如下图:

    循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。

    Spring如何解决循环依赖

    Spring容器循环依赖包括构造器循环依赖和setter循环依赖,Spring是如何解决这些循环依赖的呢?首先让我们定义循环引用类:

    public class TestA {
    
        private TestB testB;
    
        public  TestA(TestB testB){
            this.testB = testB;
        }
    
        public TestA(){
    
        }
    
        public TestB getTestB() {
            return testB;
        }
    
        public void setTestB(TestB testB) {
            this.testB = testB;
        }
    }
    public class TestB {
        private TestC testC;
    
        public  TestB(TestC testC){
            this.testC = testC;
        }
        public TestB(){
    
        }
    
        public TestC getTestC() {
            return testC;
        }
    
        public void setTestC(TestC testC) {
            this.testC = testC;
        }
    }
    public class TestC {
    
        private TestA testA;
    
        public  TestC(TestA testA){
            this.testA = testA;
        }
    
        public TestC(){
    
        }
    
        public TestA getTestA() {
            return testA;
        }
    
        public void setTestA(TestA testA) {
            this.testA = testA;
        }
    }

    (1)构造器循环依赖

    表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException 异常表示循环依赖。

    你想:在创建TestA类时,发现构造器需要TestB,那就去创建TestB,在创建TestB的时候又发现需要TestC,则又去创建TestC,最终在创建TestC时发现又需要TestA,这样就形成了一个环,没有办法创建。

    我们先通过一个直观的测试用例来进行分析:

        <bean id="testA" class="com.joe.mytag.circle.TestA">
            <constructor-arg index="0" ref="testB"/>
        </bean>
    
        <bean id="testB" class="com.joe.mytag.circle.TestB">
            <constructor-arg index="0" ref="testC"/>
        </bean>
    
        <bean id="testC" class="com.joe.mytag.circle.TestC">
            <constructor-arg index="0" ref="testA"/>
        </bean>

    建立测试类:

     public static void main(String[] args){
            try{
                new ClassPathXmlApplicationContext("spring.xml");
            }catch (Exception e){
                //因为要在创建时抛出
                e.printStackTrace();
            }
        }

    输出结果:

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testB' defined in class path resource [spring.xml]: Cannot resolve reference to bean 'testC' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testC' defined in class path resource [spring.xml]: Cannot resolve reference to bean 'testA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testA': Requested bean is currently in creation: Is there an unresolvable circular reference?
    。。。。。。

    可以看出抛出了异常。

    针对上述代码进行分析:

      ❤ Spring容器创建"testA" bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数“testB”,并将“testA”标识符放到“当前创建bean池”;

      ❤ Spring容器创建“testB” bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数“testC”,并将“testB”标识符放到“当前创建bean池”;

      ❤ Spring容器创建“testC” bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数“testA”,并将“testC”标识符放到“当前创建bean池”;

      ❤ 到止为止Spring容器要去创建“testA” bean,发现该bean标识符在“当前创建bean池”中,表示循环依赖,抛出 BeanCurrentlyCreationException。

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

    (2)setter循环依赖(默认方式:单例)

    表示通过setter注入方式构成的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。先来看一张Spring中bean的实例化步骤图:

    从图中可以看出:Spring是将bean实例化之后,再设置对象属性的。,此时的对象A还没有完成属性注入,属于早期对象,此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。

     还是先通过直观的测试来进行分析:

    修改配置文件为:

        <bean id="testA" class="com.joe.mytag.circle.TestA">
            <property name="testB" ref="testB"/>
        </bean>
    
        <bean id="testB" class="com.joe.mytag.circle.TestB">
            <property name="testC" ref="testC"/>
        </bean>
    
        <bean id="testC" class="com.joe.mytag.circle.TestC">
            <property name="testA" ref="testA"/>
        </bean>

     测试:

    public static void main(String[] args){
            try{
                ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
                System.out.println(context.getBean("testA", TestA.class));
    
            }catch (Exception e){
                e.printStackTrace();
            }
        }

    输出结果:

    com.joe.mytag.circle.TestA@e25b2fe

    发现,没有报错,分析一下上面代码的执行步骤:

      ❤  Spring容器创建单例“testA” bean,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将“testA”标识符放到“当前创建bean池”,然后通过setter注入“testB”;

      ❤ Spring容器创建单例“testB” bean,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将“testB”标识符放到“当前创建bean池”,然后通过setter注入“testC”;

      ❤ Spring容器创建单例“testC” bean,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将“testC”标识符放到“当前创建bean池”,然后通过setter注入“testA”,在进行注入“testA”时由于提前暴露了“ObjectFactory”工厂,从而使用它返回提前暴露一个创建中的bean。

      ❤ 最后再依赖注入“testB”和“testC”,完成setter注入。

    对于“singleton”作用域bean,可以通过“setAllowCircularReferences(false)”,来禁止循环引用。

    (3)setter方式原型(prototype)

     还是按照通过测试类来直观的进行分析:

    将配置文件修改为:

      <bean id="testA" class="com.joe.mytag.circle.TestA" scope="prototype">
            <property name="testB" ref="testB"/>
        </bean>
    
        <bean id="testB" class="com.joe.mytag.circle.TestB" scope="prototype">
            <property name="testC" ref="testC"/>
        </bean>
    
        <bean id="testC" class="com.joe.mytag.circle.TestC" scope="prototype">
            <property name="testA" ref="testA"/>
        </bean>

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

    测试类与上一致,这里就不贴代码了,测试结果:

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testA' defined in class path resource [spring.xml]: Cannot resolve reference to bean 'testB' while setting bean property 'testB'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testB' defined in class path resource [spring.xml]: Cannot resolve reference to bean 'testC' while setting bean property 'testC'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testC' defined in class path resource [spring.xml]: Cannot resolve reference to bean 'testA' while setting bean property 'testA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testA': Requested bean is currently in creation: Is there an unresolvable circular reference?

    发现报错了。

    那为什么原型模式就报错了呢?

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

    参考:《Spring源码深度解析》 郝佳 编著:

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    jquery.validate.js【简单实用的表单验证框架】
    velocity.js实现页面滚动切换效果
    站在巨人的肩膀上——制作酷炫web幻灯片
    简单说说随机打乱数组的方法
    JS数据结构之BinarySearchTree
    做一个extjs的扩展
    【OneAPM】极客编程挑战#025:发挥想象生成漂亮炫酷的SVG动画效果
    将博客搬至CSDN
    练习作品7:批量做字库 识别码
    联系作品6 模版打印 奖状
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/10155338.html
Copyright © 2020-2023  润新知