死磕Spring之IoC篇 - 单例 Bean 的循环依赖处理
Spring 版本:5.1.14.RELEASE
单例 Bean 的循环依赖处理
这里的循环依赖是什么?
循环依赖,其实就是循环引用,就是两个或者两个以上的 Bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。
例如定义下面两个对象:
学生类
public class Student {
private Long id;
private String name;
@Autowired
private ClassRoom classRoom;
// 省略 getter、setter
}
教室类
public class ClassRoom {
private String name;
@Autowired
private Collection<Student> students;
// 省略 getter、setter
}
当加载 Student 这个对象时,需要注入一个 ClassRoom 对象,就需要去加载 ClassRoom 这个对象,此时又要去依赖注入所有的 Student 对象,这里的 Student 和 ClassRoom 就存在循环依赖,那么一直这样循环下去,除非有终结条件。
Spring 只处理单例 Bean 的循环依赖,原型模式的 Bean 如果存在循环依赖直接抛出异常,单例 Bean 的循环依赖的场景有两种:
- 构造器注入出现循环依赖
- 字段(或 Setter)注入出现循环依赖
对于构造器注入出现缓存依赖,Spring 是无法解决的,因为当前 Bean 还未实例化,无法提前暴露对象,所以只能抛出异常,接下来我们分析的都是字段(或 Setter)注入出现循环依赖的处理
循环依赖的处理
1. 尝试从缓存中获取单例 Bean
在获取一个 Bean 过程中,首先会从缓存中尝试获取对象,对应代码段:
// AbstractBeanFactory#doGetBean(...) 方法
Object sharedInstance = getSingleton(beanName);
// DefaultSingletonBeanRegistry.java
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
// DefaultSingletonBeanRegistry.java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// <1> **【一级 Map】**从单例缓存 `singletonObjects` 中获取 beanName 对应的 Bean
Object singletonObject = this.singletonObjects.get(beanName);
// <2> 如果**一级 Map**中不存在,且当前 beanName 正在创建
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// <2.1> 对 `singletonObjects` 加锁
synchronized (this.singletonObjects) {
// <2.2> **【二级 Map】**从 `earlySingletonObjects` 集合中获取,里面会保存从 **三级 Map** 获取到的正在初始化的 Bean
singletonObject = this.earlySingletonObjects.get(beanName);
// <2.3> 如果**二级 Map** 中不存在,且允许提前创建
if (singletonObject == null && allowEarlyReference) {
// <2.3.1> **【三级 Map】**从 `singletonFactories` 中获取对应的 ObjectFactory 实现类
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 如果从**三级 Map** 中存在对应的对象,则进行下面的处理
if (singletonFactory != null) {
// <2.3.2> 调用 ObjectFactory#getOject() 方法,获取目标 Bean 对象(早期半成品)
singletonObject = singletonFactory.getObject();
// <2.3.3> 将目标对象放入**二级 Map**
this.earlySingletonObjects.put(beanName, singletonObject);
// <2.3.4> 从**三级 Map**移除 `beanName`
this.singletonFactories.remove(beanName);
}
}
}
}
// <3> 返回从缓存中获取的对象
return singletonObject;
}
这里的缓存指的就是上面三个 Map 对象:
singletonObjects(一级 Map):里面保存了所有已经初始化好的单例 Bean,也就是会保存 Spring IoC 容器中所有单例的 Spring BeanearlySingletonObjects(二级 Map),里面会保存从 三级 Map 获取到的正在初始化的 BeansingletonFactories(三级 Map),里面保存了正在初始化的 Bean 对应的 ObjectFactory 实现类,调用其 getObject() 方法返回正在初始化的 Bean 对象(仅实例化还没完全初始化好)
过程如下:
- 【一级 Map】从单例缓存
singletonObjects中获取 beanName 对应的 Bean - 如果一级 Map中不存在,且当前 beanName 正在创建
- 对
singletonObjects加锁 - 【二级 Map】从
earlySingletonObjects集合中获取,里面会保存从 三级 Map 获取到的正在初始化的 Bean - 如果二级 Map 中不存在,且允许提前创建
- 【三级 Map】从
singletonFactories中获取对应的 ObjectFactory 实现类,如果从三级 Map 中存在对应的对象,则进行下面的处理 - 调用 ObjectFactory#getOject() 方法,获取目标 Bean 对象(早期半成品)
- 将目标对象放入二级 Map
- 从三级 Map移除 beanName
- 【三级 Map】从
- 对
- 返回从缓存中获取的对象
2. 提前暴露当前 Bean
回到《Bean 的创建过程》中的“提前暴露当前 Bean”小节,在获取到实例对象后,如果是单例模式,则提前暴露这个实例对象,对应代码段:
// AbstractAutowireCapableBeanFactory#doCreateBean(...) 方法
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// <3> 提前暴露这个 `bean`,如果可以的话,目的是解决单例模式 Bean 的循环依赖注入
// <3.1> 判断是否可以提前暴露
boolean earlySingletonExposure = (mbd.isSingleton() // 单例模式
&& this.allowCircularReferences // 允许循环依赖,默认为 true
&& isSingletonCurrentlyInCreation(beanName)); // 当前单例 bean 正在被创建,在前面已经标记过
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
/**
* <3.2>
* 创建一个 ObjectFactory 实现类,用于返回当前正在被创建的 `bean`,提前暴露,保存在 `singletonFactories` (**三级 Map**)缓存中
*
* 可以回到前面的 {@link AbstractBeanFactory#doGetBean#getSingleton(String)} 方法
* 加载 Bean 的过程会先从缓存中获取单例 Bean,可以避免单例模式 Bean 循环依赖注入的问题
*/
addSingletonFactory(beanName,
// ObjectFactory 实现类
() -> getEarlyBeanReference(beanName, mbd, bean));
}
如果是单例模式、允许循环依赖(默认为 true)、当前单例 Bean 正在被创建(前面已经标记过),则提前暴露
这里会先通过 Lambda 表达式创建一个 ObjectFactory 实现类,如下:
// AbstractAutowireCapableBeanFactory.java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() // RootBeanDefinition 不是用户定义的(由 Spring 解析出来的)
&& hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
入参 bean 为当前 Bean 的实例对象(未初始化),这个实现类允许通过 SmartInstantiationAwareBeanPostProcessor 对这个提前暴露的对象进行处理,最终会返回这个提前暴露的对象。注意,这里也可以返回一个代理对象。
有了这个 ObjectFactory 实现类后,就需要往缓存中存放了,如下:
// DefaultSingletonBeanRegistry.java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
可以看到会将这个 ObjectFactory 往 singletonFactories (三级 Map)中存放,到这里对于 Spring 对单例 Bean 循环依赖的处理是不是就非常清晰了
3. 缓存单例 Bean
在完全初始化好一个单例 Bean 后,会缓存起来,如下:
// DefaultSingletonBeanRegistry.java
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
往 singletonObjects(一级 Map)存放当前单例 Bean,同时从 singletonFactories(三级 Map)和 earlySingletonObjects(二级 Map)中移除
总结
Spring 只处理单例 Bean 的字段(或 Setter)注入出现循环依赖,对于构造器注入出现的循环依赖会直接抛出异常。还有就是如果是通过 denpends-on 配置的依赖出现了循环,也会抛出异常,所以我觉得这里的“循环依赖”换做“循环依赖注入”是不是更合适一点
Spring 处理循环依赖的解决方案如下:
- Spring 在创建 Bean 的过程中,获取到实例对象后会提前暴露出去,生成一个 ObjectFactory 对象,放入
singletonFactories(三级 Map)中 - 在后续设置属性过程中,如果出现循环,则可以通过
singletonFactories(三级 Map)中对应的 ObjectFactory#getObject() 获取这个早期对象,避免再次初始化
问题一:为什么需要上面的 二级 Map ?
因为通过 三级 Map获取 Bean 会有相关 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(..) 的处理,避免重复处理,处理后返回的可能是一个代理对象
例如在循环依赖中一个 Bean 可能被多个 Bean 依赖, A -> B(也依赖 A) -> C -> A,当你获取 A 这个 Bean 时,后续 B 和 C 都要注入 A,没有上面的 二级 Map的话,三级 Map 保存的 ObjectFactory 实现类会被调用两次,会重复处理,可能出现问题,这样做在性能上也有所提升
问题二:为什么不直接调用这个 ObjectFactory#getObject() 方法放入 二级Map 中,而需要上面的 三级 Map?
对于不涉及到 AOP 的 Bean 确实可以不需要
singletonFactories(三级 Map),但是 Spring AOP 就是 Spring 体系中的一员,如果没有singletonFactories(三级 Map),意味着 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 的设计原则。Spring 是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器在完全创建好 Bean 后来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。如果出现了循环依赖,那没有办法,只有给 Bean 先创建代理对象,但是在没有出现循环依赖的情况下,设计之初就是让 Bean 在完全创建好后才完成 AOP 代理。
提示:
AnnotationAwareAspectJAutoProxyCreator是一个SmartInstantiationAwareBeanPostProcessor后置处理器,在它的 getEarlyBeanReference(..) 方法中可以创建代理对象。所以说对于上面的问题二,如果出现了循环依赖,如果是一个 AOP 代理对象,那只能给 Bean 先创建代理对象,设计之初就是让 Bean 在完全创建好后才完成 AOP 代理。
为什么 Spring 的设计是让 Bean 在完全创建好后才完成 AOP 代理?
因为创建的代理对象需要关联目标对象,在拦截处理的过程中需要根据目标对象执行被拦截的方法,所以这个目标对象最好是一个“成熟态”,而不是仅实例化还未初始化的一个对象。
- 本文链接: https://refblogs.com/article/146
- 版权声明: 本文为互联网转载文章,出处已在文章中说明(部分除外)。如果侵权,请联系本站长删除,谢谢。