Spring 是怎样解决循环依赖的?
前言
大家都知道 Spring 解决了循环依赖的问题,网上也可以搜到 Spring 是使用三级缓存来解决循环依赖的。
那大家思考过没有,Spring 为什么要用三级缓存来解决循环依赖问题?使用两级缓存行不行?只使用一级缓存行不行?
哪些循环依赖场景是 Spring 没办法解决的?
本文先分析一下, Spring 是如何通过三级缓存来解决循环依赖问题的。
三级缓存的作用是 Spring IoC 的难点,搞清楚它的原理和背后的原因非常有必要!
版本约定
Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)
正文
前面我们在分析bean 的创建过程时讲到,bean 的创建是由 AbstractBeanFactory#getBean()
触发的。
跟一下 AbstractBeanFactory#getBean(java.lang.String)
的源码,会发现它会调用 DefaultSingletonBeanRegistry#getSingleton()
// DefaultSingletonBeanRegistry#getSingleton()
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
// 一级缓存中没有 beanName, 且 beanName 正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
// bean 的早期引用中没有 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;
}
以上就是三级缓存的代码。
那我们就来好好研究一下,它是如何来解决循环依赖的!
三级缓存的定义如下:
// DefaultSingletonBeanRegistry.java
/** 一级缓存: Cache of singleton objects: bean name to bean instance. */
// 用于存放已经完全初始化好的 bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 三级缓存: Cache of singleton factories: bean name to ObjectFactory. */
// 用于存放 bean 的早期引用(都会存放,循环依赖时才会使用)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** 二级缓存: Cache of early singleton objects: bean name to bean instance. */
// 用于存放三级缓存 ObjectFactory 获取到的对象(循环依赖时才会存放并使用)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
准备例子
我们准备一个最简单的循环依赖的例子,A --> A 的场景:
@Service
public class AService {
@Autowired
AService aService;
public AService getaService() {
return aService;
}
}
public class CircleTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.kvn.beans.circle");
AService a = applicationContext.getBean(AService.class);
System.out.println(a + "--" + a.getaService());
}
}
程序可以正常运行,且 AService 中注入的 AService 是它自身。
这也就证明了 Spring 是解决了循环依赖的问题的。
循环依赖过程分析
AbstractBeanFactory#getBean()
在触发 bean 的加载时,会先从缓存中获取 bean,也就是会调用 DefaultSingletonBeanRegistry#getSingleton()
方法。
通过上面最简的例子调试 DefaultSingletonBeanRegistry#getSingleton()
,会发现:
AService 初始化过程中,第一次调用 getSingleton()
时,一级缓存 singletonObjects 中没有 AService,且 bean 也没有在创建中。
后面在 createBean()
创建 AService 的实例时,会将创建出的 bean 实例引用通过 ObjectFactory 放到三级缓存 singletonFactories 中进行暴露。
在 populateBean()
注入依赖时,会 第二次调用 getSingleton()
,此时 AService 已经在创建中,且已经存在 AService 对应的三级缓存了,通过三级缓存 ObjectFactory#getObject()
就可以获取到 bean 的早期引用 。
这时,就可以注入进去了。
事实上,第一次调用
getSingleton()
时,三个缓存中都没有 AService
三级缓存 singletonFactories 中暴露的是一个 ObjectFactory 对象,通过 ObjectFactory#getObject() 可以获取到 AService bean 对应的早期引用。
第二次调用 getSingleton()
时的断点图:
bean 在创建时,缓存的使用情况:
结合 bean 的创建过程来分析一下缓存的使用情况
bean 的创建过程分三个阶段:
1 创建实例 createBeanInstance
2 填充依赖 populateBean
3 initializeBean
下图是 bean 的创建流程:
-
在没有循环依赖的情况下:
新创建一个 bean 实例后,会先将 bean 对应的 ObjectFactory 暴露到三级缓存 singletonFactories 中。
bean 初始化完成后,会清除掉相应的二级缓存、三级缓存,同时,将初始化完成的 bean 放入一级缓存 singletonObjects 中。
-
在有循环依赖的情况下:(A --> B --> A 的场景)
A 第一次加载时,会将 A 对应的 ObjectFactory 放到三级缓存中;
当 B 创建完实例后,进行 populateBean 填充依赖时,会通过getBean(A)
来获取 bean A,这时会使用 A 对应的三级缓存 ObjectFactory 来获取 bean A 的早期引用。
三级缓存的作用是什么?
在没有循环依赖的情况下,第三级缓存只是在创建 bean 的时候单纯的存储在了 Map<String, ObjectFactory<?>> singletonFactories
中,并没有进行使用。
所以,在没有循环依赖的情况下,其实是不需要三级缓存、二级缓存的,只用一个一级缓存就可以搞定了。
那在有循环依赖的情况下,为什么不用三级缓存就搞不定呢?
第三级缓存在添加时,是通过 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
添加到 singletonFactories
中的。
当 bean 被循环依赖时,第一级缓存 singletonObjects
中还没有完全创建好的 bean;此时,第二级缓存 earlySingletonObjects
中也没有 bean 的早期引用;
所以,这时只能通过第三级缓存 singletonFactories
来获取 bean 的早期引用。
第三级缓存对应的 ObjectFactory 的实现是通过 lambda 表达式: () -> getEarlyBeanReference(beanName, mbd, bean)
来实现的,具体代码如下:
// AbstractAutowireCapableBeanFactory#getEarlyBeanReference()
/**
* Obtain a reference for early access to the specified bean, typically for the purpose of resolving a circular reference.
* 获取指定 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;
}
可以看到,第三级缓存会将 bean 的原始引用通过 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference()
来进行处理。
它只有一个实现,如下:
// AbstractAutoProxyCreator#getEarlyBeanReference()
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// 如果有需要的话,对 bean 生成代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
如果 bean 是需要被 AOP 增强的话,getEarlyBeanReference()
就会提前给 bean 的原始引用生成代理。
如果 bean 不需要被 AOP 增强的话,getEarlyBeanReference()
就不会做任何操作,直接返回原始引用。
所以,第三级缓存真正起作用的是针对 AOP 代理 bean 被循环引用的情况,这种 bean 需要提前通过第三级缓存生成 AOP 代理对象,然后放入二级缓存中。
为什么需要被 AOP 增强的 bean 需要提前生成 AOP 代理类呢?
那么,问题来了:为什么需要被 AOP 增强的 bean 需要提前生成 AOP 代理类呢?
首先,class A 的原始 bean 和 A 的代理 bean 肯定是不同的对象引用。
设想一下:假设 A --> B --> A, A 是需要被 AOP 增强的 bean 。
如果 B 在进行依赖注入时,注入的是 A 的原始 bean,而不是代理 bean 的话,那么 B 中持有的 A 没有被 AOP 增强过,那么就达不到我们预期的效果,就是有问题的。
所以,如果没有三级缓存的话,那么,对于 AOP 代理 bean 的循环依赖就解决不了。这就是三级缓存存在的核心意义!!!
小结
Spring 通过 三级缓存 为我们解决了循环依赖的问题。
三级缓存的定义和使用情况如下:
/** 一级缓存: 用于存放已经完全初始化好的 bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 二级缓存:用于存放 bean 的早期引用 */
// 非循环依赖的场景,不会存放二级缓存
// 在循环依赖的场景下,才会通过三级缓存 ObjectFactory 来获取相应的早期引用对象,放到二级缓存中使用
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** 三级缓存: 用于存放 bean 对应的 ObjectFactory */
// 不管是否有循环依赖,在 createBeanInstance 之后都会存放三级缓存
// 循环依赖时,才会使用到三级缓存,通过三级缓存来获取 bean 的早期引用对象,放到二级缓存中,同时删除掉三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
三级缓存的作用:
三级缓存是为了解决循环依赖的问题,更确切的说: 三级缓存是为了解决 AOP 代理 bean 被循环依赖的场景。
AOP 代理 bean 被循环依赖时,需要提前通过第三级缓存生成 AOP 代理对象,然后放入二级缓存中,这样就能保证最终被注入的依赖 bean 就是 Spring 最终暴露到容器中的 bean。
换句话说:当 AOP 代理 bean 被循环依赖时,需要通过第三级缓存 singletonFactories
提前获取到原始 bean 对应的 AOP 代理对象,从而将 AOP 代理 bean 的引用作为依赖注入到目标对象中。
思考:
既然 Spring 为我们解决了循环依赖的问题,但是我们有时还是会碰到循环依赖的报错:Is there an unresolvable circular reference?
那么,什么情况下 Spring 解决不了循环依赖的问题呢?
了解更多,请戳: 【Spring源码三千问】哪些循环依赖问题Spring解决不了?
SpringIoC源码视频讲解:
课程 | 地址 |
---|---|
SpringIoC源码解读由浅入深 | https://edu.51cto.com/sd/68e86 |
如果本文对你有所帮助,欢迎点赞收藏!
有关 Spring 源码方面的问题欢迎一起交流,备注:CSDN (vx: Kevin-Wang001)
阅读更多文章,请关注公众号: 老王学源码
老王读Spring IoC源码分析&测试代码下载
老王读Spring AOP源码分析&测试代码下载
公众号后台回复:下载IoC 或者 下载AOP 可以免费下载源码测试工程…
系列博文:
【老王读Spring IoC-0】Spring IoC 引入
【老王读Spring IoC-1】IoC 之控制反转引入
【老王读Spring IoC-2】IoC 之 BeanDefinition 扫描注册
【老王读Spring IoC-3】Spring bean 的创建过程
【老王读Spring IoC-4】IoC 之依赖注入原理
【老王读Spring IoC-5】Spring IoC 小结——控制反转、依赖注入
相关阅读:
【Spring源码三千问】@Resource 与 @Autowired 的区别
【Spring源码三千问】bean name 的生成规则
【Spring源码三千问】BeanDefinition详细分析
【Spring源码三千问】Spring 是怎样解决循环依赖问题的?