Spring 如何解决循环依赖
在参加互联网公司的面试时,面试官问道:“请问在Spring中,如何通过解决循环依赖的问题?
这是一个典型的高频面试题,因为一方面可以看是否对整个创建过程和原理了解,另一方面也可以看是否有自己的思考,还可以可以就此来深入源码讨论。
「前边的当我没说,其实最主要是考察下八股文背的好不好 =_= 」
今天主要针对这个问题,从是什么,为什么,怎么解决,然后结合源码进行讲述。最后补充一下有哪些注意事项。
引:循环依赖是什么
你依赖我,我又依赖你,形成了依赖环路。
以下是一个简单的循环依赖图示例:
class A {
private B b;
public A(B b) {
this.b = b;
}
}
class B {
private A a;
public B(A a) {
this.a = a;
}
}
A a = new A(new B(new A(null)));
在这个示例中,A和B类相互依赖,创建A实例时需要B实例,而创建B实例时需要A实例。
what 循环依赖会产生什么问题
这种循环依赖会导致创建实例时出现死循环,因为创建A实例需要B实例,而创建B实例需要A实例,两者互相等待对方创建完成,导致无法正常创建实例。
why 循环依赖为什么会产生这个问题
我们看下Spring 对象的创建,前边的博客里我们有提到,创建过程中包含「对象创建」和「属性填充」。
可以理解为第一步对象创建是 A a = new A() 第二步属性填充是 a.setB()。
第一步里,我们是通过对象的实例化是通过反射实现的。
第二步里,在a.setB()的过程,发现B还没有呢,就去创建B。然后创建B的过程,又发现需要A。而A也还没创建完成呢。 这咋办?
how 能解决么
分析不同情况
这个问题要分情况讨论了。
先说结论:当Bean 都是单例,并且是通过set方式注入的情况是可以解决的。分析如下:
单例多例情况
如果是Bean 都是单例
我可以通过缓存来解决。怎么理解呢,就是A和B都只有一个,A用B的时候,B虽然依赖A A没有创建好,但是没关系,B已经知道A 就这一个,我知道A 在哪,那B 就可以先用上没创建好的A 。流程照样可以进行
如果Bean 是多例
那这个问题就没法解决了。因为多例不能用缓存。每次创建都需要一个新的。这里不理解也没关系,后边会仔细讲。
构造方式和注入方式
构造方式
那也没办法解决,因为在使用构造注入方式时,对象的实例化和属性的填充是同时进行的,没办法用上缓存了。
set方式
使用set注入方式时,对象的实例化和属性的填充是分开进行的,因此可以通过缓存来解决循环依赖问题。
怎么通过缓存解决这个问题
前提:再次强调一下,经过上面的分析,这里我们讨论的范畴集中在 Spring如何通过三级缓存来解决
【单例Ben】 且【用set方式】导致的循环依赖的问题。因为别的场景,它解决不了。
那这个三级缓存怎么用的?
首先,单例的对象一定有一个缓存池,存放已经创建好的对象。这样当我们第二次创建这个对象的时候,它就直接可以从这个缓存池里来拿对象了。即一级:存放成品对象的池子
其次,如果我去拿单例A的时候,发现没有成品A,那我就去创建么?不,我还需要检查一下这个A 目前是不是已经在创建中了,比如这个对象已经初始化了,但是还没设置属性。这样我就可以先用这个创建中的A,然后后续等它属性设置好不就行了。即二级: 就是存放对象初始化好但是还没有给属性赋值的对象。
然后,如果我发现这个对象既没有一个成品,也没有在创建中。那我就去创建么?不。我还要再去看看创建这个对象的工厂是不是有了。如果已经有了,那我也先用这个工厂。即三级:存放创建这个对象的工厂。
流程
那么这三个缓存怎么配合使用呢?如图所示:
1 、是尝试从缓存中获取目标对象,如果没有获取到,则尝试获取半成品的目标对象;如果第一个步骤没有获取到目标对象的实例,那么就进入第2个步骤
2 、尝试创建目标对象,并且为该对象注入其所依赖的属性。
这里其实就是主干逻辑。
创建A对象的实例,然后递归的调用获取been, 尝试获取B对象的实例以注入到A对象中。
3 、此时由于Spring容器中也没有B对象的成品或半成品,因而会走到方框里,创建B对象的实例
创建完成之后,尝试获取其所依赖的A的实例作为其属性,此时需要注意的是,在前面由于已经有了一个半成品的A对象的实例,因而这个时候,再尝试获取A对象的实例的时候,从三级缓存中找到了一个半成品的A对象的实例,然后将该实例返回,并且将其注入到B对象的属性a中
此时B对象实例化完成。
4 、然后,将实例化完成的B对象递归的返回,此时就会将该实例注入到A对象中,这样就得到了一个成品的A对象。
源码
对应源码部分:
具体代码:抓住主干不迷路
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
doGetBean
入口
—>getSingleton
—>createBean
———> doCreateBean(beanName, mbdToUse, args)
整体 doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
...
// 1 检查3个缓存中是否有
Object sharedInstance = getSingleton(beanName);
...
// 2 没有且是单例时 创建
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
...
return createBean(beanName, mbd, args);
...
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
...
return (T) bean;
}
第1步:从缓存获取的逻辑 getSingleton
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 一级缓存 singletonObjects
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 二级缓存 earlySingletonObjects
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 三级缓存 singletonFactories
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
第2步: createBean→ doCreateBean
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
...
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
...
return beanInstance;
}
第2.1 doCreateBean 处理缓存并递归调用
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {
// 实例化当前尝试获取的bean对象,比如A对象和B对象都是在这里实例化的
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
//判断Spring是否配置了支持提前暴露目标bean,也就是是否支持提前暴露半成品的bean
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 如果支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories
// 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是
// 封装半成品的bean的地方。而这里的getEarlyBeanReference()本质上是直接将放入的第三个参数,也就是
// 目标bean直接返回
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 在初始化实例之后,这里就是判断当前bean是否依赖了其他的bean,如果依赖了,
// 就会递归的调用getBean()方法尝试获取目标bean
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
return exposedObject;
}
引申
为什么需要三级缓存,二级缓存不行么?
循环依赖是否一定需要三级缓存来解决?
答:不一定,但三级缓存会更合适,风险更小。这是为什么呢?
首先:二级缓存能否解决循环依赖? 可以,但风险比三级缓存更大
重点看:第二级缓存用来干嘛的? 存放半成品的引用,可能产生多对象循环依赖,第三级缓存产生引用后,后续的就可以直接注入该引用
多例、构造器注入为什么不能解决循环依赖?
从根本上说,因为循环依赖的原理的实例化后提前暴露的引用,这两种情况都还还没实例化
其他建议
另外为了避免循环依赖导致启动问题而又不会解决,有如下建议:
业务代码中为了简洁,尽量使用field注入而非setter方法注入
若你注入的同时,立马需要处理一些逻辑(一般见于框架设计中,业务代码中不太可能出现),可以使用setter方法注入辅助完成
总结
本篇博客主要讲一个经典面试题,Spring如何解决三级缓存问题。
Spring在实例化bean时,会先创建当前bean对象,放入缓存中,然后以递归的方式获取所依赖的属性。当注入属性时,如果出现了循环依赖则会从缓存中获取依赖对象。
另外从流程到源码做了分析,希望对大家有所帮助。