今天讲一下spring中针对单例bean的循环依赖问题,本着追本溯源的学习理念,我们要先知道单例在spring中怎么管理的。spring获取实例都通过beanfactory的getBean方法获取实例,顺着代码而下,在doGetBean方法(AbstractBeanFactory)中,单例总是通过getSingleton()方法获取缓存实例。
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
......
Object sharedInstance = getSingleton(beanName);
......
}
再来看看getSingleton方法,也非常简单,spring给了三个map,网上说是三级缓存
这段代码的含义就是 先从一级缓存中获取实例,取不到,再去取二级缓存,再没有,那就三级中取。 注意: 当从三级中取到实例时,会删除三级缓存中的值,然后放入二级缓存
。
有人问设计多级缓存的含义在哪? 这其实是为了 本文的中心思想:spring循环依赖解决方案 服务的。后面再来细讲。
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);
}
上一章我们讲了单例如何从三个缓存中去取,可能为什么要设计这个缓存我们听的云里雾里,这里我们就抛出问题:循环依赖。
我们知道,当A 有一个 属性 B , B 有一个属性A时,就形成了循环依赖,按照单例bean的创建逻辑,A没创建完,就去拿B,B还没创建完,又去拿A,请问A在哪?A还没创建完呢 ! 所以解决问题的关键在哪?spring给设计了一个多级缓存,当A完成实例化(createBeanInstance)时,这个实例已经有了自己的内存地址,只是对象不够完善,spring先把这种对象放进一个缓存。然后装配属性的时候需要B时 ,B开始自己的创建之旅,途中B需要A了,它就可以从缓存中顺利拿到实例A,即使这时候的A并不完善。 然后B把自己创建完成后,又轮到A继续完善自己,直到 A和B 都创建完毕,这样就没有了循环依赖的问题。
下面用图反映spring bean的大致创建流程 与 多级缓存之间的配合:
在了解了循环依赖的基本原理后,我当时第一个疑问就是:为什么要设计三层缓存?而不是两层,理论上,设计两层就能满足需求。
我们来反过来看看最开始根据getSingleton获取单实例的方法,有一个参数allowEarlyReference,这个为true,就说明你可以从 三级缓存 (singletonFactories) 来获取提前曝光的实例,如果为false,你最多只能从二级缓存(earlySingletonObjects)来获取实例。那哪里 会用false来调用getSingleton方法呢?
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);
}
我们从doCreateBean方法中看到如下代码,这段代码在装配完bean后(populateBean)开始执行,会看到其中调用了getSingleton(beanName, false),这里其实是 解决循环依赖中的二次检查,我们举个例子:A和B相互关联,当A初始化的时候,发现需要B,然后A作为提前曝光的bean放入三级缓存。然后创建B,B发现需要A,从三级缓存(singletonFactories)中顺利取出,放入二级缓存(earlySingletonObjects),然后继续创建B,B创建完毕,继续初始化A,这时, A因为被BeanPostProcessors处理过,导致返回了一个完全不一样的对象
,这时进入下面的代码进行二次检查,getSingleton因为allowEarlyReference参数为false 的缘故,只能最多从二级缓存中取数据,发现还真找到了,从二级缓存中取出也说明了 对象A存在被其它对象依赖的情况,当然也包括循环依赖的情形,对于循环依赖来说,对象的创建过程中不允许被二次修改,即使是BeanPostProcessors处理过也不能返回内存地址不一样的对象,想想这样也是对的,这样才能保证全程的对象是一致的。 这也说明了二级缓存的作用--如果你能从二级缓存中拿到对象,就说明它是被其它对象依赖的,在经过BeanPostProcessors 等方法的处理后,也要二次检查来保证对象的一致性。
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}