循环依赖即两个及以上的bean对象互相持有对方的引用,最终形成一个闭环。
Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持
在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出
BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
public class BeanA {
private BeanB b;
public BeanA(BeanB b) {
this.b = b;
}
}
public class BeanB {
private BeanC c;
public BeanB(BeanC c) {
this.c = c;
}
}
public class BeanC {
private BeanA a;
public BeanC(BeanA a) {
this.a = a;
}
}
----------------------
----------------------
ApplicationContext context = new ClassPathXmlApplicationContext("bean.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?
Spring容器先创建单例BeanA,BeanA依赖BeanB,然后将A放在“当前创建Bean池”中,此时创建BeanB,BeanB依赖BeanC ,然后将B放在“当前创建Bean池”中,此时创建BeanC,StudentC又依赖BeanA, 但是,此时BeanA已经在池中,所以会报错,,因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)
public class BeanA {
public BeanB b;
public void setB(BeanB b) {
this.b = b;
}
}
public class BeanB {
public BeanC c;
public void setC(BeanC c) {
this.c = c;
}
}
public class BeanC {
public BeanA a;
public void setA(BeanA a) {
this.a = a;
}
}
----------------------
----------------------
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
BeanA a = context.getBean("a", BeanA.class);
BeanB b = context.getBean("b", BeanB.class);
BeanC c = context.getBean("c", BeanC.class);
System.out.println(String.format("a:%s,a.b:%s",a,a.b));
System.out.println(String.format("b:%s,b.c:%s",b,b.c));
System.out.println(String.format("c:%s,c.a:%s",c,c.a));
输出:
a:com.raycloud.dmj.data.utils.BeanA@46bfc63c,a.b:com.raycloud.dmj.data.utils.BeanB@586fb16d
b:com.raycloud.dmj.data.utils.BeanB@586fb16d,b.c:com.raycloud.dmj.data.utils.BeanC@ce99877
c:com.raycloud.dmj.data.utils.BeanC@ce99877,c.a:com.raycloud.dmj.data.utils.BeanA@46bfc63c
可以看到单例setter循环依赖没有报错,且循环的依赖都成功set。具体实现原理后面详细看
同样报错:
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?
因此可以知道多例模式下也不能解决循环依赖。
为什么?
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容
器不进行缓存,因此无法提前暴露一个创建中的Bean。
如图,set方法注入,spring先实例化Bean[通过无参构造器],在设置属性,这样就不会报错了。
具体如何解决循环依赖
以我们的实例来说,spring会先实例化a,b,c,并放入一个map,然后设置属性,a设置属性b,只需要从mao中取出b即可,以此类推。
而事实上spring可不止这一个map,而是通过三级缓存来解决单例Bean的循环依赖。
/** Cache of singleton objects: bean name --> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
singletonObjects:存放单例对象实例的缓存
earlySingletonObjects:存在提前曝光的bean,也就是正在创建中的bean。
singletonFactories :创建单例对象的工厂
实现原理的代码如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先从单例缓存中取
Object singletonObject = this.singletonObjects.get(beanName);
//一级缓存中没有,并且判断正在创建中【比如A的构造器依赖B,或者已经实例化正在setB ,所以先去创建B,那么A就是创建中】
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//再二级缓存中查找,在正在创建中的对象缓存中找
singletonObject = this.earlySingletonObjects.get(beanName);
//如果找不到,并且允许去单例工厂创建就去单例工厂创建
if (singletonObject == null && allowEarlyReference) {
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
//移除三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
简单来说:
在创建单例Bean的时候是这样解决循环依赖的。
假设A B互相依赖。
先通过createBean创建A,会先走createInstance来实例化A,然后把A的单例工厂放到三级缓存,实例化后需要设置属性,发现需要B,但是B没有初始化,因此通过createBean创建,同样需要实例化,实例化以后发现依赖A,因此先去单例缓存中找,因为A还在创建中,所以找不到,然后去二级缓存找,依旧找不到,因此最后通过单例工厂创建获取了A,B就创建好了,B创建好了,就set给A,此时A.B都实例化成功了。