前言
上篇我们讲了bean的初始化流程,要搞懂循环依赖,必须要先清除流程,不清楚的请移步上一篇文章。
这篇我们就来讲讲Spring是如何解决循环依赖的
一、什么是循环依赖
循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。如下图所示:
循环依赖,其实就是一个死循环的过程,在初始化 A 的时候发现引用了 B,这时就会去初始化 B,然后又发现 B 引用 C,跑去初始化 C,初始化 C 的时候发现引用了 A,则又会去初始化 A,依次循环永不退出,除非有终结条件。
1. 循环依赖有几种情况
有两种
- 基于构造函数的注入方式的循环依赖
- 基于属性注入的循环依赖
Spring只能解决属性注入的循环依赖,构造函数注入方式的循环依赖是没法解决的,原因等我们分析完循环依赖后再来个总结.
其实倒不是说不能使用构造函数注入,而且Spring从4.3开始推荐使用构造函数注入的方式,举例具体来说
A 依赖 B, B又依赖A
下面这几种情况
- A中采用构造函数注入依赖B, B中采用构造函数依赖A, 无法解决
- A中采用构造函数注入依赖B, B中采用属性注入A, 可以解决
- A中采用属性注入依赖B, B中采用构造函数依赖A 无法解决
- A中采用属性注入依赖B. B中采用属性注入A 可以解决
细心的异能已经发现了,只要B中采用构造函数注入A,就无法解决,A采用什么方式则没什么影响
下面我们以A,B 都采用属性注入的方式且无AOP代理的情况下来看Spring是如何解决循环依赖的,这也是最常见的方式
二、循环依赖的解决
1.getSingleton(beanName)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
话不多说,直接上图,看不清的点击放大来看
但是这只是最简单的循环依赖的一种情况, 有AOP的会更加复杂,以后有机会再来更新
2 为什么不能解决构造函数注入方式的循环依赖
如果熟悉了循环依赖的解决流程,那么这个问题自然也很好回答, 因为如果采用构造函数注入的方式,那么会去看构造函数的参数是否初始化,如果没有会初始化这个bean,如此一来,便陷入了死循环,没法提前曝光半初始化的bean了,
说的简单点就是,指定了构造函数就不会走默认的无参构造,就无法走下面的提前曝光,加入三级缓存,也就无法解决循环依赖了
2.疑问
这个疑问我在上篇文章中提过,就是这段代码
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<>(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 " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
这里也与循环依赖有关
//到这里又会走getSingleton(beanName, false),是不是很熟悉,没错就是我们进doGetBean时一开始会走的从缓存中取bean的方法
//不过之前传的是true,这里传的是false
//先提前说明下这个方法的流程,举例说明
//对于循环依赖: A依赖B, B依赖A,且属性注入(构造函数注入无法解决嘛)
//初始化A, 走到填充属性时,会去初始化B, 由于B中又依赖的A, 又去getBean(A), 此时就会走 getSingleton(beanName, true)
//而此时容器中一,二级缓存中没有A实例的,只有三级缓存中有,就会把三级缓存中那个半初始化的beanA取出(注意:此时会把A放在的二级缓存中,从三级缓存中移除)
//然后B的属性填充完毕, 然后走初始化完成, 当B走到下面这段代码时, 即走到getSingleton(beanName, false)时, 一二级缓存均为空,
//但是传参是false,所以Object earlySingletonReference 为null,那么返回的就是初始化完成的exposedObject
//即exposedObject = initializeBean(beanName, exposedObject, mbd);
// 最后后会把初始化好的B放入一级缓存中
//然后继续走之前的A属性填充(留意一下,A填充好了),初始化,但是当A走到这段代码时,因为之前往二级缓存中放了A,
//所以此时取二级缓存不为空,会返回缓存中的这个引用,而且如果这个引用和增强后的bean是一个对象实例,那么会返回这个引用
//此时有人可能会有疑问: 缓存中的这个bean不是半初始化的么,怎么返回使用了呢?
//这是因为,二级缓存中存放的是A的引用,在之前这个引用指向的对象实例被填充了啊,这就是之前让你留意一下的原因
//不过读到这里我有个疑问: 为什么Spring要再去缓存中取一下这个bean呢?我直接使用初始化返回的那个bean嘛,就是exposedObject,
//为什么要现在这样设计呢?还查下缓存,然后再比较bean有没有变,变了返回exposedObject, 没变返回缓存中的bean
//我看有的博客说的是此处代码的作用是: 循环依赖检查,判断是否需要抛出异常
//存疑?有知道的小伙伴可以留言告诉我,我感觉这里是和AOP代理有关