Spring循环依赖的三种方式以及解决办法
LD is tigger forever,CG are not brothers forever, throw the pot and shine forever.
Modesty is not false, solid is not naive, treacherous but not deceitful, stay with good people, and stay away from poor people.
talk is cheap, show others the code,Keep progress,make a better result.
Survive during the day and develop at night。
目录
概述
Spring循环依赖的三种方式以及解决办法
实现思路分析
什么是循环依赖
循环依赖其实就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
Spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性的循环依赖
其中,构造器的循环依赖问题无法解决,只能拋出BeanCurrentlyInCreationException异常,在解决属性循环依赖时,spring采用的是提前暴露对象的方法。
如何检测是否存在循环的依赖
检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。
Spring容器的会将每一个正在创建的bean标示符放在“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException 异常表示循环依赖;
而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
Spring 容器先创建单例A,A依赖B,然后将A放在“当前创建Bean池”中,此时创建B,B依赖C,C又依赖A
但是,此时A已经在池中,所以会报错,因为在池中的Bean都是未初始化完的,所以会依赖错误 。
3种循环依赖
//@Scope(value = "prototype")
@Component
public class StudentA {
@Autowired
private StudentB studentB;
public StudentA() {
}
//@Autowired
public StudentA(StudentB studentB) {
this.studentB = studentB;
}
//@Autowired
public void setStudentB(StudentB studentB) {
this.studentB = studentB;
}
public void say() {
System.out.println("我是学生A -> 依赖学生B|" + studentB);
}
}
使用@componment
//@Scope(value = "prototype")
@Component
public class StudentB {
@Autowired
private StudentC studentC;
public StudentB() {
}
//@Autowired
public StudentB(StudentC studentC) {
this.studentC = studentC;
}
//@Autowired
public void setStudentC(StudentC studentC) {
this.studentC = studentC;
}
public void say() {
System.out.println("我是学生B -> 依赖学生C|" + studentC);
}
}
//@Scope(value = "prototype")
@Component
public class StudentC {
@Autowired
private StudentA studentA;
public StudentC() {
}
//@Autowired
public StudentC(StudentA studentA) {
this.studentA = studentA;
}
//@Autowired
public void setStudentA(StudentA studentA) {
this.studentA = studentA;
}
public void say() {
System.out.println("我是学生C -> 依赖学生A|" + studentA);
}
}
上面是很基本的3个类,通过Field字段、Constructor构造器、Setter方法,产生了一个循环依赖的情况。
上面是很基本的3个类,StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况。
我们都把这三个Bean交给Spring管理,并用有参构造实例化
<bean id="a" class="com.liuqing.student.StudentA">
<constructor-arg index="0" ref="b"></constructor-arg>
</bean>
<bean id="b" class="com.liuqing.student.StudentB">
<constructor-arg index="0" ref="c"></constructor-arg>
</bean>
<bean id="c" class="com.liuqing.student.StudentC">
<constructor-arg index="0" ref="a"></constructor-arg>
</bean>
setter方式单例,默认方式
1.实例化bean对象
2.设置对象属性
3.检查Aware相关接口并设置相关依赖,beanPOSTProcess前置处理
4.检查是否是InitializingBean以决定是否调用afterProperties
5.检查是否配置自定义的init-method
6.BeanPostProcessor后置处理
7.注册必要的Destruction相关回调接口,使用
8.是否实现DisturbposableBean的接口
9.是否有自定义的destoty方法
Spring是先将Bean对象实例化【依赖无参构造函数】
再设置对象属性的
<bean id="a" class="com.StudentA" scope="prototype">
<property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="com.StudentB" scope="prototype">
<property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="com.StudentC" scope="prototype">
<property name="studentA" ref="a"></property>
</bean>
scope=“prototype”,每次请求都会创建一个实例对象。
有状态的bean都使用Prototype作用,无状态的一般都使用
singleton单例作用域。
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
四。spring 怎么解决循环依赖
Spring的循环依赖的理论依据基于Java的引用传递,当获得对象的引用时候,对象的属性是可以延后设置的。
Spring的单例对象的初始化主要分为三步:
从上面单例bean的初始化可以知道,循环依赖主要发生在第一、二步,也就是构造器循环依赖和field循环依赖。
对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
这三级缓存分别指:
singletonObjects:
原因分析
singletonObjects:单例对象的cache
earlySingletonObjects :提前暴光的单例对象的Cache
singletonFactories : 单例对象工厂的cache
在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。
如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许
singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍singletonFactories个三级cache。
这个cache的类型是ObjectFactory。解决循环依赖的关键。
createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。所以Spring此时将这个对象提前曝光出来让大家认识,让大家认识使用。
工作原理(重点)
1.A的某个field或者setter依赖了B的实例对象
2.同时B的某个field或者setter依赖了A的实例对象
首先:
A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,
3.所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,
相关工具如下:
分析:
小结:
主要讲述了Spring循环依赖的三种方式以及解决办法, 里面有许多不足,请大家指正~