spring解决循环依赖问题
Posted 唐僧洗澡不秃头
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring解决循环依赖问题相关的知识,希望对你有一定的参考价值。
一、简介:
循环依赖就是循环引用,就是两个或多个bean互相之间持有对方。比如beanA引用beanB,beanB引用beanA,当我们实例化beanA的时候发现beanB作为beanA的成员对象出现了,那么我们就可能在实例化beanA的中间需要先实例化beanB,然后完成beanB的实例化之后,才能完成beanA的实例化;可惜的是beanB中也引用了beanA,在实例化beanB过程中又需要实例化beanA,而beanA正在进行实例化,但完成beanA的实例化的条件是beanB实例化完成,完成beanB实例化的条件是完成beanA的实例化,于是他们最终反映为一个环状依赖,难以完成实例化。
二、spring中循环依赖的三种情况
1、构造器注入形成的循环依赖
beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成初始化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。
2、setter注入构成的循环依赖
beanA需要在beanB的setter方法中完成初始化,beanB也需要在beanA的setter方法中完成初始化,spring设计的机制主要就是解决这种循环依赖,也是今天要讨论的重点。
3、prototype作用域bean的循环依赖
这种循环依赖同样无法解决,因为spring不会缓存prototype作用域的bean,而spring中循环依赖的解决方式正是通过缓存来实现的。
三、spring对应循环依赖的解决
spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field是可以延后设置的。spring单例对象的初始化其实可以分为三步:实例化、填充属性、初始化。
实例化(createBeanInstance):就是调用对应的构造方法构造对象,此时只是调用了构造方法,并没有进行属性填充。
填充属性(populateBean):填充属性。
初始化(initializeBean):调用spring xml中指定的init方法,或者AfterPropertiesSet方法。
四、spring中的三级缓存
对于单例对象来说,在spring的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在Cache中,spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至使用了”三级缓存“。
// 一级缓存
private final Map<String,Object> singletonObjects = new ConcurrentHashMap<>(256);
// 三级缓存
private final Map<String,ObjectFactory<?>> singletonFactories = new HashMap<>();
// 二级缓存
private final Map<String,Object> earlySingletonObjects = new HashMap<>(16);
一级缓存:singletonObjects,存放完成实例化,且属性赋值完成的单例对象的cache,直接可以使用。
二级缓存:earlySingletonObjects,存放提前曝光的单例对象的cache,尚未进行属性封装的Bean。
三级缓存:singletonFactories,存放单例对象工厂的cache。
五、spring如何解决循环依赖
spring在创建bean的工程中,首先会尝试从缓存中获取,这个缓存指的就是上面的singletonObjects(一级缓存),主要调用的方法如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
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 != NULL_OBJECT ? singletonObject : null);
}
isSingletonCurrentlyInCreation():判断对应的单例对象是否在创建中,(例如BeanA在填充属性的过程中依赖了BeanB对象,得先去创建BeanB对象,此时BeanA处于创建中)。
allowEarlyReference():是否允许从singletonFactories(三级缓存)中通过getObject拿到对象。
分析下getSingleton()的整个过程:
步骤一:
spring首先从singletonObjects(一级缓存)中尝试获取。
步骤二:
如果在一级缓存中获取不到并且对象处于创建中状态,则尝试从earlySingletonObjects(二级缓存)中获取。
步骤三:
如果还是获取不到并且允许从singletonFactories(三级缓存)通过getObject()获取,则通过singletonFactory.getObject()(三级缓存)获取。如果获取到了则移除对应的singletonFactory,将singletonObject放入到earlySingletonObjects(二级缓存),其实就是将三级缓存提升到二级缓存中!
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
spring解决循环依赖的诀窍就在于singletonFactories(三级缓存)这个cache,这个cache中存的是类型为ObjectFactory,其定义如下:
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
在bean创建过程中,有两处比较重要的匿名内部类实现了该接口,上面已经提到了,spring利用他来创建bean
new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
}
另一处就是:
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
此处就是解决循环依赖的关键,这段代码发生在createBeanInstance(实例化)之后,也就是说单例对象此时已经被创建出来的。这个对象已经被生产出来了,虽然还不完美(还没有进行填充属性和初始化),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
六、总结:
BeanA的某个field或者setter依赖了BeanB的实例对象,同时BeanB的某个field或者setter依赖了BeanA的实例对象,出现这种循环依赖的情况。
BeanA首先完成了初始化的第一步(实例化),并且将自己放到singletonFactories(三级缓存)中,此时进行初始化的第二步(属性填充),发现自己依赖对象BeanB,此时就尝试去get(BeanB),发现BeanB还没有被create,所以走create流程。
BeanB首先完成初始化第一步(实例化),然后在属性填充的时候发现自己依赖了对象BeanA,于是尝试get(BeanA),尝试从一级缓存singletonObjects(肯定没有,因为BeanA还没初始化完全),尝试从二级缓存earlySingletonObjects(也没有),尝试从三级缓存singletonFactories中获取,由于BeanA通过ObjectFactory将自己提前曝光了,所以BeanB能够通过ObjectFactory.getObject()拿到BeanA对象(虽然BeanA还没有初始化完全,但是可以被发现),
BeanB拿到BeanA对象后顺利完成了初始化阶段1、2、3(实例化、填充属性、初始化),完全初始化之后将自己放入到一级缓存singletonObjects中。并且将BeanA放到二级缓存中,移除三级缓存中的BeanA。
此时返回BeanA中,BeanA此时能拿到BeanB的对象顺利完成自己的初始化阶段2、3(填充属性和初始化),最终BeanA也完成了初始化,将BeanA也添加到了一级缓存singletonObjects中。
知道了这个原理时候,肯定就知道为啥Spring不能解决“BeanA的构造方法中依赖了BeanB的实例对象,同时BeanB的构造方法中依赖了BeanA的实例对象”这类问题了!因为BeanB连初始化的第一步(实例化)都完成不了,无法进行下一步的操作。
以上是关于spring解决循环依赖问题的主要内容,如果未能解决你的问题,请参考以下文章
spring的循环依赖是什么,设计的时候怎么避免,而spring又是怎么解决的?