Spring如何解决循环依赖

对自己说的话

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

什么是循环依赖

所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A。它们之间的依赖关系如下:
在这里插入图片描述
根据创建对象的方式不同, 分为三种情况:

  • 第一种:构造器参数循环依赖
  • 第二种: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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值