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