一. 前言
1. Spring版本:5.2.4.BUILD-SNAPSHOT
2. 本文着重点
二. 概念明晰
1. 半成品bean
2. 循环依赖问题
3. 三级缓存引入
三. 三级缓存的变更过程
1. a实例化后,但尚未填充属性b(获得半成品a)
2. b实例化后,但尚未填充属性a(获得半成品b)
3. 第3次调用getSingleton()时
4. 回溯过程中,b成为成品时
5. 回溯过程中,a成为正品时
总结三级缓存的变化
四. 从源码层面梳理三级缓存解决循环依赖的整个流程
五. 循环依赖中加入了AOP
1. 三级缓存各自的作用
2. 简单总结没有AOP的循环依赖的整个流程
3. 加入AOP之后循环依赖的整个流程
3. getEarlyBeanReference()
4. initializeBean()
五. 要点(面试考点)分析
1. 只用二级缓存能解决循环依赖问题吗?
2. 只用一级缓存能解决循环依赖问题吗?
1. 梳理解决循环依赖的整个流程(流程比较长,源码很长,陷得很深,着重于理清整个流程,不要抠细节)
2. 三级缓存的变化(要理清三级缓存如何变更)
3. 关键代码验证(不要过于抠细节)
由于源码跟踪很难用图片去说明,因此涉及到核心代码,我会:标注出类名和方法名。至于如何追踪到某处代码,自己跟一遍源码。
为了后面的讲述便于理解,在此之前先明晰几个下问将会出现多次的概念。
已经完成实例化(通过反射调用空参构造),但是还没有初始化(指没有填充属性。XML方式通过setter注入,注解方式@Autowired等)
如图所示:
此处,以一个最简单的例子来引入循环依赖问题。后文的整个流程也是基于该例子!
两个或则两个以上的对象互相依赖对方,最终形成 闭环 。此处,我们以 "A依赖B,B依赖A" 为例,且"依赖"是属性依赖。如下图所示:
假如没有三级缓存,基于上述例子,Spring的整个创建的流程大致如下图所示:
此处先作引入,对三级缓存的定义有明确认识即可。至于有啥用,后文再说。
所谓"三级缓存",就是 DefaultSingletonBeanRegistry 类中的 3 个 Map
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// 一级缓存。value装的是成品bean
/** Cache of singleton objects: bean name to bean instance. */
private final Map singletonObjects = new ConcurrentHashMap<>(256);
// 三级缓存。value装的是函数式接口的具体实例,核心就是里面的方法。因此可以简单地理解为装的就是一个lambda表达式
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map> singletonFactories = new HashMap<>(16);
// 二级缓存。value装的是半成品bean
/** Cache of early singleton objects: bean name to bean instance. */
private final Map earlySingletonObjects = new HashMap<>(16);
}
注意:三级缓存的顺序(或者说名字由来)是由查询顺序而来,而与DefaultSingletonBeanRegistry类中的定义顺序无关
见 DefaultSingletonBeanRegistry 类 中的下面核心方法。后面会多次遇到该方法!
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先查一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
// 没有一级缓存,看bean是否正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 再查二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 最后查三级缓存
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 执行lambda AOP
// 从三级缓存中根据beanName取出的value是ObjectFactory对象,执行它的方法。
singletonObject = singletonFactory.getObject();
// 放入二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 移除对应的三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
将a放入三级缓存中
将b放入三级缓存中
创建a时调用一次getSingleton();
为了填充a中的属性b时,又调用 doGetBean('b'),随后又调用了一次getSingleton();
为了填充b中的属性a时,又调用 doGetBean('a'),随后又调用了一次getSingleton()。
第3次调用getSingleton(),将a放入二级缓存,并从三级缓存移除,最终会返回半成品a,并开始漫长的回溯!!!
注:灰色表示已经移除
在回溯完b的创建过程,此时b就正式成为成品了。但是a的创建过程尚未完成,a还是半成品!
将b放入一级缓存,并三级缓存中移除
此时,a和b都是正品了,循环依赖已经解决!回溯过程接近尾声。
将a放入一级缓存,并二级缓存中移除
看完上面的示意图,可以发现【在这个例子中】:
b至始至终没有放入过二级缓存
b虽然生成过三级缓存,但是没有用就remove掉了
1. 入口:DefaultListableBeanFactory中的 preInstantiateSingletons() 方法
循环BeanDefinition,逐个创建bean
第一次循环,调用了getBean() 创建 a。从getBean() 开始,深入解决循环依赖的整个流程!
完整清晰流程图:https://www.processon.com/view/link/604a4ffde0b34d091d970bd4
密码:Spring
在"三"中,明确交代了三级缓存的变化过程。结合"四"中详细的流程图和注释,相信大家对整个过程都比较清晰了。但是对于一些细节问题,我们还没有理解清楚。为了说明循环依赖加入了AOP之后,流程中的哪些地方发生了关键性的变化,我先简单总结一下【没有AOP的循环依赖】的整个处理流程。如果面试官问到:Spring如何使用三级缓存解决循环依赖。可以参考一下下面的回答:
为了便于理解,我先说清楚:在哪个地方发生了关键性的变化。之后再去源码中做验证!
假设 a, b 均为AOP代理对象:
从上面描述中,我们知道了,要么在 getEarlyBeanReference() 生成代理对象,要么在 initializeBean() 生成代理对象。下面,从这两处地方切入,以上面加入AOP的循环依赖以自来debug,找一下关键代码!
自己点进去找一下,就找找到创建代理对象的关键代码了。记住这个类,下面initializeBean()时也会遇到!!!
进入 initializeBean()
假如没有配置AOP,二级缓存就能解决循环依赖问题。
假如配置AOP,要想只用二级缓存解决循环依赖解决循环依赖问题,需要将 代理对象的生成提前到实例化后,初始化前。
参考:https://www.cnblogs.com/youzhibing/p/14337244.html
如果将半成品对象和成品对象都混在一级缓存中,那么为了区分它们,势必会增加一些额外的标记和判断逻辑处理,这就会导致对象的创建过程变得复杂化了,且时间复杂度也会增加,没必要。将半成品对象与成品对象分开存放,两级缓存各司其职,能够简化对象的创建过程,更简单、直观。