(一)Spring IoC源码-3.其他特性的实现原理-02循环依赖的解决

什么是循环依赖

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。

Spring中循环依赖场景有哪些
  1. 构造器循环依赖
  2. setter循环依赖
怎么检测是否存在循环依赖

抛出了BeanCurrentlyInCreationException异常?

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

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

第一种:构造器参数循环依赖

构造器参数循环依赖是无法解决的。

首先我们先初始化三个Bean。

public class StudentA {  
  
    private StudentB studentB ;  
  
    public void setStudentB(StudentB studentB) {  
        this.studentB = studentB;  
    }  
  
    public StudentA() {  
    }  
      
    public StudentA(StudentB studentB) {  
        this.studentB = studentB;  
    }  
}  

ublic class StudentB {  
  
    private StudentC studentC ;  
  
    public void setStudentC(StudentC studentC) {  
        this.studentC = studentC;  
    }  
      
    public StudentB() {  
    }  
  
    public StudentB(StudentC studentC) {  
        this.studentC = studentC;  
    }  
}  

public class StudentC {  
  
    private StudentA studentA ;  
  
    public void setStudentA(StudentA studentA) {  
        this.studentA = studentA;  
    }  
  
    public StudentC() {  
    }  
   
    public StudentC(StudentA studentA) {  
        this.studentA = studentA;  
    }  
}  

StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,
我们都把这三个Bean交给Spring容器管理,并用有参构造实例化。

<bean id="studentA" class="com.pww.student.StudentA">  
    <constructor-arg index="0" ref="studentB"></constructor-arg>  
</bean>  
<bean id="studentB" class="com.pww.student.StudentB">  
    <constructor-arg index="0" ref="studentC"></constructor-arg>  
</bean>  
<bean id="studentC" class="com.pww.student.StudentC">  
    <constructor-arg index="0" ref="studentA"></constructor-arg>  
</bean>

下面是测试类:

public class Test { 
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/pww/student/applicationContext.xml");  
    }  
}  

执行结果报错信息为:

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?  

针对以上代码的分析如下:

  1. Spring容器先创建studentA,首先去“当前创建bean池”里查找当前bean是否正在创建,如果没有找到,则将studentA标识符放到池子里,并准备创建studentA所需要的构造器参数studentB;
  2. Spring容器创建studentB,首先去“当前创建bean池”里查找当前bean是否正在创建,如果没有找到,则将studentB标识符放到池子里,准备创建studentB所需要的studentC;
  3. Spring容器创建studentC,首先去“当前创建bean池”里查找当前bean是否正在创建,如果没有找到,则将studentC标识符放到池子里,准备创建studentC所需要的studentA;
  4. 此时发现studentA已经在“当前创建bean池”里了,所以会抛出BeanCurrentlyInCreationException。
第二种:setter注入,单例

Spring是先将Bean对象实例化之后再设置对象属性的

为了避免循环依赖,Spring采取了一种将正在创建的bean实例提早暴露加入到singletonFactories缓存中。

修改配置文件为set方式注入

<!--scope="singleton"(加不加都行,默认就是单例方式)  -->  
<bean id="studentA" class="com.pww.student.StudentA" scope="singleton">  
    <property name="studentB" ref="studentB"></property>  
</bean>  
<bean id="studentB" class="com.pww.student.StudentB" scope="singleton">  
    <property name="studentC" ref="studentC"></property>  
</bean>  
<bean id="studentC" class="com.pww.student.StudentC" scope="singleton">  
    <property name="studentA" ref="studentA"></property>
</bean>

下面是测试类:

public class Test { 
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("com/pww/student/applicationContext.xml");  
        System.out.println(context.getBean("studentA", StudentA.class));  
    }  
}  

打印结果为:

com.pww.student.StudentA@1fbfd6  

为什么用set方式就不报错了呢 ?针对以上代码的分析如下:

  1. Spring容器先创建单例bean,studentA,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个暴露的正在创建中的bean,将studentA标识符放到“当前创建bean池”里,然后setter注入studentB;
  2. Spring容器创建单例bean,studentB,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个暴露的正在创建中的bean,将studentB标识符放到“当前创建bean池”里,然后setter注入studentC;
  3. Spring容器创建单例bean,studentC,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个暴露的正在创建中的bean,将studentC标识符放到“当前创建bean池”里,然后setter注入studentA;
  4. setter注入studentA时,发现studentA已经提前暴露了,使用“ObjectFactory”返回正在创建中的studentA;
  5. 注入studentA、studentB,完成。
第三种:setter注入,原型

这种循环依赖,Spring无法解决。
修改配置文件为:

<bean id="studentA" class="com.pww.student.StudentA"  scope="prototype">  
        <property name="studentB" ref="studentB"></property>  
</bean>  
<bean id="studentB" class="com.pww.student.StudentB"  scope="prototype">  
    <property name="studentC" ref="studentC"></property>  
</bean>  
<bean id="studentC" class="com.pww.student.StudentC"  scope="prototype">  
    <property name="studentA" ref="studentA"></property>  
</bean> 

测试用例:

public class Test {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("com/pww/student/applicationContext.xml");  
        //此时必须要获取Spring管理的实例,因为scope="prototype" ,只有请求获取的时候才会实例化对象
        System.out.println(context.getBean("studentA", StudentA.class));  
    }  
}  

打印结果:

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

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

总结
  1. 构造器参数循环依赖是无法解决的。 Spring容器会将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持
    在池中。因此如果在创建bean过程中发现当前bean已经在“当前创建bean池”里时将抛出
    BeanCurrentlyInCreationException异常表示循环依赖。对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
  2. Spring可以解决setter注入&单例bean的循环依赖。为了避免循环依赖,Spring采取了一种将正在创建的bean实例提早暴露加入到singletonFactories缓存中。
  3. Spring无法解决setter注入&原型bean的循环依赖。因为“prototype”作用域的bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的bean。

完。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值