深圳幻海软件技术有限公司 欢迎您!

最通俗的方式理解Spring循环依赖三级缓存

2023-02-28

今天,有位粉丝找我,说要耽误我5分钟时间,想让我帮助它理解一下Spring循环依赖的三级缓存,绕晕了一个星期,没有想明白。我想今天,用最通俗易懂的方式给大家重新梳理一下,保证让你听懂了。1、什么是循环依赖?循环依赖就是指循环引用,是两个或多个Bean相互之间的持有对方的引用。循环依赖有三种形态:(1

今天,有位粉丝找我,说要耽误我5分钟时间,想让我帮助它理解一下Spring循环依赖的三级缓存,绕晕了一个星期,没有想明白。我想今天,用最通俗易懂的方式给大家重新梳理一下,保证让你听懂了。

1、什么是循环依赖?

循环依赖就是指循环引用,是两个或多个Bean相互之间的持有对方的引用。循环依赖有三种形态:

(1)相互依赖,也就是A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。

(2)三者间依赖,也就是A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖。

(3)自我依赖,也是A依赖A形成了循环依赖自己依赖自己。

2、如何解决循环依赖问题?

循环依赖本身没有问题,问题是Spring中加入了依赖注入机制,也就是自动给属性赋值。当创建Bean实例化以后,需要给Bean中需要赋值的属性全部自动赋值才能交给用户使用。但如果是循环依赖的情况,以两个Bean相互依赖的情况为例,

假设Bean A已经实例化,但是Bean A中需要自动赋值Bean B并没有初始化,但如果Spring立刻去初始化Bean B,发现Bean B中需要自动赋值的Bean A没有初始化,如果这样相互等待,就会形成死循环,最终,有可能导致Spring容器都无法启动。

就好比,我们以前读书的时候,老师经常教我们一个考试方法,就是遇到难题不会答的时候,不要死磕,要继续往下做其他的题。否则,会因为一道难题卡住影响到整个的答题进度,还会影响正常的发挥,影响考试结果。

那这个问题该怎么解决呢?使用缓存。

就是将所有实例化好的Bean,全部放到一个容器中缓存起来,并且将已经完成实例化但没有完成赋值的,打上标记。

然后,等Bean全部实例化以后,再重新扫描一遍容器,将没有完成赋值的Bean属性完成赋值,这个时候,所有未完成赋值的Bean都已经能够找到对应的实例了。

那么问题来了。解决循环依赖问题,一定要二级缓存吗?答案是不一定。但是Spring中为什么又要设计二级缓存呢?

这时候,我们可以这样理解,假设,我们只有一个缓存容器,并且缓存是直接开放给用户可以调用的,如果将未完成赋值的Bean和已完成赋值的Bean全部放到同一个容器,那这个时候,调用者就有可能拿到未赋值的Bean,这样的Bean对于用户来说是不可用的,可能会导致空指针异常。

所以,Spring设计者,才有了这样一个设计,将能够直接提供给用户使用的Bean放到一级缓存中,这样Bean称之为终态Bean,或者叫成熟Bean。

将已经完成初始化,但还不能提供给用户使用的Bean单独放到一个缓存容器中,就是二级缓存,这样的Bean称之为临时Bean,或者叫早期Bean。

依照以上的分析,理论上二级缓存就能解决循环依赖问题,那为什么Spring还要设计一个三级缓存呢?

3、如何理解三级缓存?

我们都知道,Spring中有很多注入的Bean是需要创建代理Bean的,但是,不是所有的Bean都需要再实例化之后立马就会创建代理Bean。是要等到Bean初始化全部完成之后才创建代理Bean。因此,循环依赖的出现,Spring又不得不去提前创建代理Bean。如果不创建代理Bean,注入原始Bean就会产生错误。因 此,Spring设计三级缓存,专门用来存放代理Bean。但是,创建代理Bean的又不同的规则,因此,Spring三级缓存中,并不是直接保存代理Bean的引用,而是保存创建代理Bean的Factory。

4、总结

所以,总结结论为,单纯解决循环依赖可以只用二级缓存,但是如果涉及到代理对象的循环依赖,就需要用到三级缓存。其实一、二、三级缓存是根据获取对象的顺序来命名的,我们完全可以这样理解,一级缓存就是终态缓存,二级缓存是临时缓存、三级缓存是代理工厂的缓存。

这张图完整地描述了一、二、三级缓存的运行逻辑。