说到 Spring 循环依赖,大家应该并不陌生吧。今天就简单对这个问题进行研究。
什么是 Spring 循环依赖
创建A 实例中需要依赖 B,而 B 实例创建又依赖 A 实例,形成一个闭环,这就是 Spring 中的循环依赖。
循环依赖的检测与解决
Spring 循环依赖有几种?
原型的循环依赖【无法解决】
单例 bean 的循环依赖 - 构造参数依赖产生【无法解决】
单例 bean 的循环依赖 - setter 产生依赖 【可以解决】
循环依赖检测过程
刚刚我们了解了,Spring 循环依赖中原型循环依赖和单例构造参数依赖是无法解决的。虽然无法解决,但是我们还是要了解一下 Spring 是如何检测出来的。接下来就开始分析源码了。 首先,我们要从 refresh#finishBeanFactoryInitialization 开始,因为这里实例化所有非原型的 bean。然后又调用了 beanFactory.preInstantiateSingletons(); 接下来我们介绍完前提开始真正的一步一步跟源码了。
1. 首先获取合并后的 BeanDefinition 信息
接下来通过判断 bd,推断出当前 bean 是否是抽象的,是否是单例的,是否是懒加载的。如果不是抽象的,是单例的但不是懒加载的,那么就继续往下走,调用 getBean#doGetBean 方法。
2. 到缓存中去查询,因为当前对象未初始化过,一定查不到
由于一级缓存里没有改 bd 的信息,所以返回 null。
3. 在 else 中又获取了一次合并后的 bd(mbd),确定 mbd 可以创建实例,即 mbd 为非抽象的。
因为抽象的 bd 只能被继承使用,不能去实例化。
4. 优先加载当前 mbd 依赖的 DependsOn BeanDefinition
DependsOn 用于声明当前 bean 依赖于另外一个bean,所依赖的 bean 会被容器确保在当前 bean 实例化之前被实例化。
单例模式-构造参数依赖
A5. 尝试去一级缓存里拿,一定拿不到
A6. 记录当前 beanName 到 CreationSet 中
getSingleton#beforeSingletonCreation
A7. 开始创建实例对象
createBean
|--> doCreateBean
|--> createBeanInstance
通过 class 拿到 Constructor 构造器,进行反射创建对象。
A8. 发现构造器有一个参数类型为 B
但是构造方法可以是有参数,也有可能是无参数,我们先说有参数。就像开篇描述循环依赖一样 A 中有 B,那么我们在执行 createBeanInstance 方法时就要先去实例化 B。
A9. 需要先实例化 B
然后我们有重新执行了上面的步骤,只不过这次是执行的 B 类型。
B.7、B.8、B.9
通过 class 拿到 Constructor 构造器,进行反射创建对象
| ——>发现构造器有一个参数类型 A
|——> 需要先实例化 A
然后又回去了[尴尬],回到上面 4.优先加载 mbd。 A5. 尝试去一级缓存里拿,一定拿不到(1~4 不变,从第五步说) A6. 检测出循环依赖 终于发生变化了。记录当前 BeanName 到 CreateSet 中.. 但是发现 add 失败了。原来之前 set 中已经插入过 A 了,检测出循环依赖。
抛出循环依赖异常 那么单例 bean 的循环依赖 - 构造参数依赖 就检测出来了。
原型的循环依赖检测 接下来是原型的循环依赖检测,我们从 AbstractBeanFactory.doGetBean 开始。
在循环依赖检测的前 4 步是不变的,我们直接从第五步开始。
A5` 向 threadLocal 里添加当前 beanName
在上面代码中我们可以看到,进入 else if 里的时候,里面有一个 beforePrototypeCreation 的方法,该方法就是先去 prototypesCurrentlyInCreation(一个 threadLocal)中去获取,如果没有则添加。
A6` 通过 Class 拿到 Constructor 构造器,将要进行反射创建对象
A7` 发现构造器有一个参数类型 B
A8` 需要先实例化 B
然后经历了像之前单例构造器循环依赖一样的步骤,实例化 B 的过程又发现有 A,去创建 A。在创建 A 的过程中又会调用 doGetBean(),然后会走如下代码:
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
·····
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
在进入 isPrototypeCurrentlyInCreation 方法中,会对 prototypesCurrentlyInCreation 里是否含有 A 进行判断,如果有则返回 true。那么返回 true 后会抛循环依赖异常!
循环依赖解决过程
我们知道,单例 bean 的循环依赖(setter 产生依赖)是可以解决的。那我们来简单分析一下: 前四步和之前一样,这里我直接从第五步开始。
5. 去一级缓存里拿,一定拿不到。
doGetBean
|--> getSingleton
|--> beforeSingletonCreation //这个方法我们很熟悉了吧。
6. 记录当前 beanName 到 prototypesCurrentlyInCreation 中
通过 beforeSingletonCreation,我们将 beanName 放入 threadLocal中。
7. 通过 class 拿到无参 Cunstructor 构造器,进行反射创建对象
8. 反射创建完毕,此时得到一个早期对象
什么是早期对象?(半成品)
① 未进行属性注入(无参构造)
② 未进行对象 init 方法调用
③ 未进行后处理器处理
但早期对象和处理完成后的对象内存地址是一样的,都是同一个对象。
9. 执行 MegerBeanDefinitionPostProcessor 操作,如处理 @Autowire 等
小知识,@Autowire 是 setter 注入。
10. 将早期对象封装成 ObjectFactory,放到三级缓存中
11. 进行依赖注入 populateBean
try {
//属性填充,自动注入 ---通用方法
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
12. 这时发现依赖了 B 类型,进行 getBean(B) 操作
接下来的操作和 实例化A 是一样的,B 也将自己的早期实例对象放到三级缓存中。然后属性注入 populateBean,发现 B 又依赖了 A,进行 getBean(A)操作。
getBean
|--> doGetBean
|--> getSingleton
@Nullable
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;
}
然后又获取合并后的 bd 信息。 接下来就像 getSingleton 一样 ,先去一级缓存中拿,拿不到;检查 CurrentlyInCreation,发现 A 正在创建;去三级缓存中发现了 A 的早期实例;最后将 A 升级到二级缓存并清空三级缓存后返回 A 对象。 那么 B 的依赖注入完成了,最终将对象 B 放至一级缓存,并且清除掉二三级有关 B 的信息。
13. 完成依赖注入
接上面第 12 步,注入 B 完成。
14. 最终
A 的依赖注入完成了,最终将对象 A 放至一级缓存,并且清除掉二三级有关 A 的信息。
至此,单例无参依赖注入解决完毕。
结束
依赖注入从检测到解决的所有过程就全部结束了,面试按照这个说绝对没问题。由于没有图,可能只是文字描述会很生涩,我会在后面继续对循环依赖做补充,顺便解决很多简写的东西。毕竟如果全部展开太多了。这也是我第一篇 Spring 文章写循环依赖的原因,因为它能牵扯出太多东西。 最后感谢观看,如果有问题请和我一起探讨一起进步。
感谢 B 站【小刘讲源码】 space.bilibili.com/457326371/