【史上最详细,没有之一】Spring【三级缓存】解决【循环依赖】的流程梳理,原理和核心代码分析

目录

一. 前言

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. 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如何使用三级缓存解决循环依赖。可以参考一下下面的回答:

  1. a实例化完成之后(a成为半成品),将a放入三级缓存
  2. 初始化a(给a填充属性b),又去创建b
  3. b实例化完成之后(b成为半成品),将b放入三级缓存
  4. 初始化b(给b填充属性a),又去从容器中获取a
  5. 此时可以从三级缓存中查到a。将半成品a放入二级缓存,并从三级缓存中移除a。最终返回半成品a。开始回溯。
  6. b的创建过程回溯完之后,b成为正品,将b从三级缓存中移除,将b放入一级缓存。而a还是半成品。(b中的属性a的属性b还是null,即:b.a.b=null)
  7. a的创建过程回溯完之后,a也成为正品,将a放入一级缓存,并从二级缓存中移除。循环依赖已解决,a和b均创建成功

3. 加入AOP之后循环依赖的整个流程

为了便于理解,我先说清楚:在哪个地方发生了关键性的变化。之后再去源码中做验证!

假设 a, b 均为AOP代理对象:

  1. a实例化完成之后(a成为半成品),将a放入三级缓存
  2. 初始化a(给a填充属性b),又去创建b
  3. b实例化完成之后(b成为半成品),将b放入三级缓存
  4. 初始化b(给b填充属性a),又去从容器中获取a
  5. 此时可以从三级缓存中查到a的ObjectFactory对象,调用getObject()(实际上调用了getEarlyBeanReference())生成了半成品a的代理对象)。半成品a的代理对象放入二级缓存,并从三级缓存中移除。注意:此时是在初始化b,却将半成品a的代理对象提前创建出来,并返回。开始回溯。
  6. 给b填充完属性a之后,初始化尚未结束。随后调用 initializeBean() 完成b的初始化,并创建了成品b的代理对象
  7. b的创建过程回溯完之后,b成为正品,将b从三级缓存中移除,将成品b的代理对象放入一级缓存
  8. a的创建过程回溯到 initializeBean() ,由于a的代理对象已经在二级缓存了,故无需重新创建代理对象了。a的创建过程回溯完之后,直接将a的代理对象从二级缓存移到一级缓存即可,标记a完成创建。此时一级缓存中,均是a、b的成品代理对象。

从上面描述中,我们知道了,要么在 getEarlyBeanReference() 生成代理对象,要么在 initializeBean() 生成代理对象。下面,从这两处地方切入,以上面加入AOP的循环依赖以自来debug,找一下关键代码!

3. getEarlyBeanReference()

自己点进去找一下,就找找到创建代理对象的关键代码了。记住这个类,下面initializeBean()时也会遇到!!!

4. initializeBean()

进入 initializeBean() 


五. 要点(面试考点)分析

1. 只用二级缓存能解决循环依赖问题吗?

假如没有配置AOP,二级缓存就能解决循环依赖问题。

假如配置AOP,要想只用二级缓存解决循环依赖解决循环依赖问题,需要将 代理对象的生成提前到实例化后,初始化前。

参考:https://www.cnblogs.com/youzhibing/p/14337244.html

2. 只用一级缓存能解决循环依赖问题吗?

如果将半成品对象和成品对象都混在一级缓存中,那么为了区分它们,势必会增加一些额外的标记和判断逻辑处理,这就会导致对象的创建过程变得复杂化了,且时间复杂度也会增加,没必要。将半成品对象与成品对象分开存放,两级缓存各司其职,能够简化对象的创建过程,更简单、直观。

 

 

  • 7
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值