欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring如何解决循环依赖

程序员文章站 2022-07-12 08:13:35
...

对自己说的话

深入spring原理对现在的自己来说确定太难了,但是要坚持,点滴的积累,一定会有收获的~加油!

什么是循环依赖

所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A。它们之间的依赖关系如下:
Spring如何解决循环依赖
根据创建对象的方式不同, 分为三种情况:

  • 第一种:构造器参数循环依赖
  • 第二种:setter方式单例,默认方式
  • 第三种:setter方式原型,prototype,也就是多例

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

Spring容器会将每一个正在创建的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;
    }
}
public 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;
    }
}

上面是很基本的3个类,,StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,
我们都把这三个Bean交给Spring管理,并用 有参构造实例化对象到容器中

 	<bean id="a" class="com.zuoyueer.StudentA">
		<constructor-arg index="0" ref="b"></constructor-arg>
	</bean>
	<bean id="b" class="com.zuoyueer.StudentB">
		<constructor-arg index="0" ref="c"></constructor-arg>
	</bean>
	<bean id="c" class="com.zuoyueer.StudentC">
		<constructor-arg index="0" ref="a"></constructor-arg>
	</bean> 

下面的测试类:

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

执行结果报错信息为:

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池"查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数"StudentB",并将"StudentA “标识符放置到"当前创建bean池”.

  2. Spring容器创建"StudentB" bean,首先去"当前创建bean池"查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数"StudentC",并将"StudentB"标识符放置到"当前创建bean池".

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

  4. 到此为止Spring容器要再次去创建"StudentA " bean,发现该bean标识符在"当前创建bean池"中,因为表示循环依赖,故抛出BeanCurrentlyInCreationExpection异常。因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)

第二种:setter方式单例,默认方式

修改配置文件为set方式注入,scope="singleton"表示单例,默认就是单例,可以不设置

	<!--scope="singleton"(默认就是单例方式)  -->
	<bean id="a" class="com.zuoyueer.StudentA" scope="singleton">
		<property name="studentB" ref="b"></property>
	</bean>
	<bean id="b" class="com.zuoyueer.StudentB" scope="singleton">
		<property name="studentC" ref="c"></property>
	</bean>
	<bean id="c" class="com.zuoyueer.StudentC" scope="singleton">
		<property name="studentA" ref="a"></property>
	</bean>

下面是测试类:

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

打印结果为:(正常的创建了对象,没有异常)

com.zuoyueer.StudentA@F646AE

分析原因:

  1. 容器安装配置顺序,先去实例化"StudentA",首先会根据无参构造器创建一个bean,并储存该对象的引用储存到容器中, 这个引用是"StudentA对象的一个早期的引用(early reference), 注意: 此时setter注入器还未被调用,这个对象是没有给属性赋值的.
  2. 再去实例化"StudentB",也会根据无参构造器创建一个bean,并把对象的早期的引用(early reference)储存到容器中.
  3. 再去实例化"StudentC", 因为"StudentC"依赖于"“StudentA” , 这时容器中有"StudentA"的对象的一个早期的引用(early reference),那么就调用setter注入器, 把"StudentA"对象的早期引用注入到"StudentC"对象中
  4. 以上过程是递归完成是,所以"StudentC"实例化之后,会接着调用"StudentB"对象的setter注入器,最后调用"StudentA"对象的setter注入器,完成对象中属性的赋值.也就是依赖注入,

为了理解这个早期的引用(early reference),我们需要了解Spring的三级缓存

缓存 用途
singletonObjects 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
earlySingletonObjects 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
singletonFactories 存放 bean 工厂对象,用于解决循环依赖

实际上, 根据无参构造器创建一个bean,并将此类对应的ObjectFactory暴露出去,也就是注册到singletonFactories中, 从而使其他的bean能引用到该bean.当你依赖到了该Bean而单例缓存里面有没有该Bean的时候就会调用该工厂方法生产Bean,此时setter注入器还未被调用

为什么不把Bean直接暴露出去,而是暴露个Factory呢?因为有些Bean是需要被代理的.

以上过程最关键的一点是Spring是先将Bean对象实例化(无参构造)之后再设置对象属性的

关于3级缓存,推荐一个博主的博客:https://blog.csdn.net/f641385712/article/details/92801300

第三种:setter方式原型,prototype,也就是多例

修改配置文件为:```scope=“prototype”``表示多例,也就是每次请求的时候都创建一个新的对象

	<bean id="a" class="com.zuoyueer.StudentA" scope="prototype">
		<property name="studentB" ref="b"></property>
	</bean>
	<bean id="b" class="com.zuoyueer.StudentB" scope="prototype">
		<property name="studentC" ref="c"></property>
	</bean>
	<bean id="c" class="com.zuoyueer.StudentC" scope="prototype">
		<property name="studentA" ref="a"></property>
	</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?

分析原因:

过程和第二种方式差不多,只不过,在调用setter注入器的时候,在缓存中找不到被暴露出来的工厂对象(找不到依赖的早期引用).
这是因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

多例模式是几乎不会用到的,在用的时候也要避免循环依赖, 并且对于"singleton"作用于的bean,我们可以通过"setAllowCircularReferences(false)"来禁止循环引用.


总结:

对于Spring循环依赖的情况总结如下:
不能解决的情况:

  • . 构造器注入循环依赖
  • . prototype field属性注入循环依赖(多例)

能解决的情况:

  • . field属性注入(setter方法注入)循环依赖(单例)

参考资料:
https://blog.csdn.net/u010644448/article/details/59108799
https://blog.csdn.net/Taylar_where/article/details/90612724
https://www.imooc.com/article/34150

相关标签: 循环依赖