Spring - 循环依赖
1. 循环依赖是什么?
循环依赖,顾名思义,指的是两个或者多个Bean之间相互依赖,形成了一个闭环。在Java代码中,循环依赖通常如下形式:
java
public class A {
private B b;
// get set 方法
}
public class B {
private A a;
// get set 方法
}
在这个例子中,类A依赖于类B,而类B也依赖于类A,形成了一个依赖的闭环,这就是所谓的循环依赖。
2. 什么情况下循环依赖可以被处理?
💡 先阅读 3. Spring循环依赖源码分析 在思考什么情况下 `****循环依赖可以被处理****`
在使用Setting方法进行属性注入时,Spring可以处理 循环依赖
问题。
如果使用构造方法进行属性注入,有概率会出现不能处理的情况。
先加载的Bean属性,是通过Setting进行属性注入的,那循环依赖就可以被处理。
3. Spring循环依赖源码分析
在开始分析Spring解决循环依赖问题前我们不如先思考思考,一般在业务系统中我们是怎么样实现数据缓存的。
💡 大致实现流程 → 获取时, 先判断在缓存中是否存在内容信息,存在就直接进行返回。缓存中不存在时,先获取想要的内容后,再设置到缓存中。
为什么要提这个?因为Spring解决依赖源码就是通过缓存的方式来实现的。
💡 先简单回顾一下 Bean 对象的创建过程
- 实例化,对应方法:**
AbstractAutowireCapableBeanFactory
中的createBeanInstance
**方法 - 属性注入,对应方法:**
AbstractAutowireCapableBeanFactory
的populateBean
**方法 - 初始化,对应方法:
AbstractAutowireCapableBeanFactory
的initializeBean
doGetBean方法
在创建Bean时, 最终都指向 doGetBean 方法来获取到Bean对象。
那既然是缓存,那肯定在这个地方补充最适合不过了,代码中的 getSingleton()
就是Spring对于单利Bean的缓存实现了
既然是缓存的时候,那在开始之前, 先简单介绍下 Spring中的一级、二级、三级缓存中分别都存储了那些东西:
- **
一级缓存(singletonObjects)
**存储已经创建完成的单例Bean信息 - **
二级缓存(earlySingletonObjects)
**存储通过三级缓存(singletonFactories),获取到的Bean对象信息。 - **
三级缓存(singletonFactories)
**存储实例化成功的Bean,通过 ObjectFactory 可以获取到Bean实例化对象。在AOP时, 会一个AOP的代理对象,而不用原始的Bean实例化对象了。
💡 看完以后感觉很懵逼,二级缓存和三级缓存在某种程度上其实可以理解为是一个东西。
这个时候小明发出的疑问:
markdown
那为什么不直接在实例化bean以后就支持设置到二级缓存中呢?哪怕是AOP其实也可以拿着时候预先创建好代理对象,并设置到二级缓存中,完全不需要使用3级缓存。
针对小明的疑问发出惊呼, 说的好。
先通过一个简单的执行流程图,看看在 getSingleton
方法中到底做了哪些事情。
- 先通过BeanName,判断一级缓存(singletonObjects)中是否已经存在Bean实例。一级缓存中存储的都是已经创建完成的Bean单例对象,存在时直接返回即可。
- 在二级、三级缓存时,需要先判断当前Bean是不是属于正在创建中的实例对象。二级、三级缓存主要是为了处理循环依赖而设计。存储的是已经实例化成功的Bean,但是还没有完成属性注入。先存储这些Bean实例对象,在循环依赖发生时可以提前注入。
- 先通过三级缓存可以获取到Bean实例对象,再设置到二级缓存中,避免三级缓存重复创建。
在大致了解了缓存判断逻辑后。在来阅读源码就比较简单了,源码如下:
java
// 获取缓存信息
// beanName bean的名称
// allowEarlyReference 是否启用三级缓存, 只有在 `doGetBean` 也就刚获取Bean时才会为true. 其他场景下都是 false
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 一级缓存 (singletonObjects) : 已经创建完成的单例 Bean 对象
Object singletonObject = this.singletonObjects.get(beanName);
// 当一级缓存不存在的时候, 有两个场景
// 1. Bean还未进行创建, 这个就没有在考虑二、三级缓存了
// 2. Bean已经在创建了, 但是还未完全创建成功(填充字段属性阶段)这个阶段的Bean会存在循环依赖的可能
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 二级缓存 (earlySingletonObjects) : Bean实例对象,介入AOP切面的Bean存储的是代理对象
singletonObject = this.earlySingletonObjects.get(beanName);
// 三级缓存 (singletonFactories) : 主要是针对AOP的扩展, 如果被AOP切面则会返回代理对象
// 可以理解为 Bean 实例化后的对象
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
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) {
// 获取三级缓存中存储的Bean实例化对象, 并设置到二级缓存中, 编辑三级缓存重复调用
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
通过上述分析,大致对于对Spring一级、二级、三级缓存及Spring如何处理循环依赖的有了一个较为清晰的认知。那Spring的一级、二级、三级缓存是在什么时候进行设置的呢?
💡 在思考问题前,不妨我们先简单回顾一下Bean的生命周期
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 销毁 Destruction
那 "循环依赖" 问题发生在那个阶段呢?没错是在 "属性赋值" 阶段。在二级、三级缓存的处理必然是在 "属性赋值"之前,"实例化" 之后了。
💡 实例化阶段,通过构造方法注入。在一定场景下会导致Spring循环依赖处理失败。
有了具体的思路以后,我们在看看具体的方法 doCreateBean
方法,是Spring创建单例Bean的实现,代码如下:
java
// 单利Bean创建过程
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 第一部分: BeanDefinition转化为Bean实例
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// Bean对象信息
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// ........ 缺省 .........
// **是否允许循环依赖, 单利Bean & 允许循环依赖 & 当前Bean正在创建中**
// **所以的单利Bean都会在这里进行缓存**
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 在 **addSingletonFactory** 方法中, Spring把创建好的Bean对象, 存储到了三级缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// ........ 缺省 .........
}
通过 createBeanInstance
获取到Bean实例后,在允许循环依赖下,所有单例的Bean都会执行 addSingletonFactory
方法,然后在进行后续初始化操作。是的对于三级缓存的实现就是在 addSingletonFactory
方法中。
addSingletonFactory()
在 addSingletonFactory
方法中,发现Spring 把Bean 实例化对象,都会在 singletonFactories
三级缓存中进行补充。所以重点我们应该观察在 三级缓存 singletonFactories
中到底存储的是什么?
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);
}
}
}
getEarlyBeanReference()
可以看到在三级缓存中,存储的实际上是一个 函数试接口
内部执行了执行了 SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()
方法,那在方法中具体是做了哪些事呢?
我个人理解是Spring对Bean实例化后的扩展,可以通过 getEarlyBeanReference
来调整 Bean的实例化
对象。
结合一下场景,一般什么情况下会需要改变 Bean的实例化
,是的。在引用AOP后,会需要 代理对象
。设想一下,有一个被AOP代理的Bean,如果在 三级缓存
中存储的是自己本身的Bean,而不是其 代理对象
,那在属性注入的时候,就存在问题了。
💡 具体实现AOP可以看 AbstractAutoProxyCreator.getEarlyBeanReference(),返回了提前创建好的代理对象。
java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
// 非AOP场景下, getEarlyBeanReference其实是不会被执行的, 而是直接返回 Bean 对象信息
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
// for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
// exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
// }
}
return exposedObject;
}
到此为止,Spring在处理循环依赖的相关流程,相信也有较为清晰的理解。
💡 个人小总结之Spring处理循环依赖概述: 在Spring中,循环依赖问题一般发生在属性注入的时候,为了解决该问题。Spring会把Bean对象,在实例化后,属性注入之前。先存储到三级缓存当中去。如果真出现循环依赖问题,那也可以从三级缓存中提前获取到Bean对象,进行属性注入,从而避免循环依赖问题。但是在使用 `构造方法` 注入场景下,有概率会出现失效问题。是因为构造方法注入时,Bean还没有完成实例化,三级缓存中也就不会存在该Bean,就引发了注入问题。但是也不是绝对的,比如 A 通过 Setting 方式注入,B 通过构造方法注入,这个时候就不会有问题。因为Spring会先加载A,然后再去加载B,三级缓存中已经存在A对象信息了。