11.单例 Bean 的循环依赖处理
11.单例 Bean 的循环依赖处理
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读
Spring 版本:5.1.14.RELEASE
开始阅读这一系列文章之前,建议先查看《深入了解 Spring IoC(面试题)》这一篇文章
该系列其他文章请查看:《死磕 Spring 之 IoC 篇 - 文章导读》
单例 Bean 的循环依赖处理
我们先回到《Bean 的创建过程》中的**“从缓存中获取单例 Bean”小节,当加载一个 Bean 时,会尝试从缓存**(三个 Map)中获取对象,如果未命中则进入后面创建 Bean 的过程。再来看到《Bean 的创建过程》中的**“提前暴露当前 Bean”小节,当获取到一个实例对象(还未设置属性和初始化)后,会将这个“早期对象”放入前面的缓存中(第三个 Map),这里暴露的对象实际是一个 ObjectFactory,可以通过它获取“早期对象”。这样一来,在后面设置属性的过程中,如果需要依赖注入其他 Bean,且存在循环依赖,那么上面的缓存**就避免了这个问题。接下来,将会分析 Spring 处理循环依赖的相关过程。
访点击这里:100万QPS短链系统、复杂的微服务系统实战、商城系统实战、秒杀系统实战、代码生成工具实战、工作经验分享、技术选型、系统设计、性能优化、源码解读、高频面试题,这里什么都有
点击这里获取:接口、Java、JVM、并发编程、MySQL、Redis、ElasticSearch、Spring、SpringBoot等性能优化技巧
点击这里获取:我10年工作中,遇到过的100个常见的问题(实际上至少总结了200多个问题),以及相关的解决方案,非常有参考价值
这里的循环依赖是什么?
循环依赖,其实就是循环引用,就是两个或者两个以上的 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 的创建过程》中的**“从缓存中获取单例 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 Bean
- earlySingletonObjects(二级 Map),里面会保存从 三级 Map 获取到的正在初始化的 Bean
- singletonFactories(三级 Map),里面保存了正在初始化的 Bean 对应的 ObjectFactory 实现类,调用其 getObject() 方法返回正在初始化的 Bean 对象(仅实例化还没完全初始化好)
过程如下:
1、 【一级Map】从单例缓存singletonObjects中获取beanName对应的Bean;
2、 如果一级Map中不存在,且当前beanName正在创建;
1、 对singletonObjects加锁;
2、 【二级Map】从earlySingletonObjects集合中获取,里面会保存从三级Map获取到的正在初始化的Bean;
3、 如果二级Map中不存在,且允许提前创建;
1. **【三级 Map】**从 `singletonFactories` 中获取对应的 ObjectFactory 实现类,如果从**三级 Map** 中存在对应的对象,则进行下面的处理
2. 调用 ObjectFactory\#getOject() 方法,获取目标 Bean 对象(早期半成品)
3. 将目标对象放入**二级 Map**
4. 从**三级 Map**移除 beanName
3、 返回从缓存中获取的对象;
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 代理?
因为创建的代理对象需要关联目标对象,在拦截处理的过程中需要根据目标对象执行被拦截的方法,所以这个目标对象最好是一个“成熟态”,而不是仅实例化还未初始化的一个对象。
版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: