前言
大家都知道 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);
// 这里一个有意思的点,AService的beanName是AService而不是aService
//AService test = (AService) context.getBean("AService");
System.out.println(a + "--" + a.getaService());
}
}
注意,
new AnnotationConfigApplicationContext("com.kvn.beans.circle");已经完成了容器的创建,bean的实例化;
applicationContext.getBean(AService.class)只是从缓存拿bean,
程序可以正常运行,且 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 的创建流程:
populateBean里通过反射去创建属性的实例,比如 @Autowired属性注入:
实际调AutowiredAnnotationBeanPostProcessor#postProcessProperties完成注入,源码往下走:
创建实例test,注入了test1,上图光标就是去实例化test1的源码位置,调了 beanFactory.getBean(beanName)去创建test1
调试代码:
@Component
public class Test {
@Autowired
Test1 test1;
}
@Component
public class Test1 {
}
public static void main(String[] args) {
// 创建容器,读取配置,创建bean,
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext("com.tiger.springbean");
context.getBean("test");
}
这里只是简单的源码跟踪下populateBean注入属性时,在哪里实例化了属性,方便后续去调试循环依赖的各种场景。
在没有循环依赖的情况下:
新创建一个 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 代理类呢?
首先,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 解决不了循环依赖的问题呢?
————————————————
版权声明:本文为CSDN博主「老王学源码」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wang489687009/article/details/120523242