【编程底层原理】一文彻底搞懂Spring是如何利用三级缓存来解决循环依赖问题的(一级缓存为啥解决不了,二级缓存可以解决为啥也不合适,三级缓存为啥合适)


在这里插入图片描述

一、整体推导思路

为了彻底搞懂Spring是如何利用三级缓存来解决循环依赖问题的,要么去找三级缓存的设计者了解其设计的初衷,要么利用反推法来进行倒推(即一级缓存为啥不行,二级缓存为啥也不合适)。
为了让大家能有一个更清晰的理解脉路,下面将先从反推法来介绍下一级缓存为啥不行、二级缓存为啥也不合适,然后再介绍为啥三级缓存适合解决循环依赖的问题,通过前后对比,理解起来也会更加清晰明了。
看过很多关于三级缓存的资料,都是从使用三级缓存的角度来阐述的,这样就背离了我们要解决的问题的初衷,我们要弄明白的是三级缓存的设计思路来源,势必需要从三级缓存缓存的时机、缓存的内容以及一二三级缓存的流转这个设计的角度来进行解剖。否则,最终也只是大概了解了三级缓存的每一级缓存是用来干嘛的,但是不清楚为啥有这三级缓存,其底层的原理逻辑是啥,知其然,而非知其所以然。就很被动了。

二、缓存的四个基本问题

另外,讲到缓存,都必定涉及到以下四个基本问题:

问题1:为啥要用缓存,即缓存的作用?

回答:为了加速不同使用者对共享资源的访问。通过把不需要多次创建的共享资源(如数据库连接、日志记录对象等),在其首次被创建后,按照key(资源名称)-value(资源实体)的方式缓存起来,以便下次能快速根据key来检索到该资源,而非再次创建。

问题2:何时进行缓存?

回答:当该对象是业务上需要缓存的共享资源,且首次被创建出来后,即可进行缓存

问题3:缓存的内容是啥?

回答:缓存的内容是资源实体对象。

问题4:缓存啥时候失效?

回答:当该资源实体对象在系统中没有存在的必要了;或者构成资源实体的基本信息有变化,需要根据变化后的基本信息来重新构建时,旧的缓存就失效了。
在Spring循环依赖这个讨论背景下,共享资源就是Spring容器管理的bean对象。

三、一级缓存为啥解决不了循环依赖的问题(缓存完整的bean对象)

1、一级缓存解决不了循环依赖

如果设计成一级缓存,若存在循环依赖时,两个bean对象相互依赖,都需要拿到对方的bean对象,才能进行自身bean对象的创建,这时仅通过一级缓存的设计方案(完整的bean对象),两个bean对象的创建动作将永远无法结束,就像死锁一样无限期的死等下去。

2、一级缓存存在资源浪费问题

而且,每个bean对象在创建完成后,无论后续是否被会被其他bean对象引用,都会无脑存放到一级缓存中,随着bean对象的不断增加,缓存的内容越来越多,势必对系统资源造成越来越大的压力。

四、二级缓存可以解决为啥也不合适(缓存尚未进行属性注入的早期bean对象)

1、二级缓存能解决循环依赖问题

如果设计成二级缓存,若存在循环依赖,两个bean对象相互依赖,因为有二级缓存的存在,不需要再等到构建出完整的bean对象,而是可以提前获取到尚未进行属性注入的早期的bean对象,这时两个bean对象的创建动作将不会因为获取不到对方的bean对象就无限期死等下去,而是可以正常进行下去。
注意:二级缓存是可以解决Spring的循环依赖问题,但是还存在资源消耗的问题以及当涉及代理对象时对象创建和对象缓存的职责耦合的复杂度问题。

2、二级缓存存在资源浪费问题

但是,同一级缓存一样,在构造出的尚未进行属性注入的早期bean对象后,无论后续是否被会被其他bean对象引用,都会无脑存放到二级缓存中,随着bean对象的不断增加,缓存的内容越来越多,势必也会对系统资源造成越来越大的压力。

3、二级缓存存在【代理对象创建及对象缓存职责耦合的复杂度问题】

而且,因为有可能涉及到代理对象的逻辑,所以如果只设计成二级缓存,那么代理对象的创建和对象缓存这两个职责势必会耦合在一块,代理逻辑复杂度也会上升。

五、三级缓存为啥合适(缓存构建bean的工厂对象)

1、三级缓存能解决循环依赖

如果设计成三级缓存,若存在循环依赖,两个bean对象相互依赖,因为有二级缓存的存在,不需要再等到构建出完整的bean对象,而是可以提前获取到尚未进行属性注入的早期的bean对象,这时两个bean对象的创建动作将不会因为获取不到对方的bean对象就无限期死等下去,而是可以正常进行下去。

2、三级缓存不存在资源浪费和【代理对象创建及对象缓存职责耦合的复杂度问题】

而且,因为有三级缓存的存在,只有在其他bean对象需要该bean对象的时候,才会到三级缓存获取bean工厂开始bean对象的缓存(此时可以进行代理对象的处理,如果是代理对象则创建代理对象并放到二级缓存,否则通过反射获取原始对象放到二级缓存,并清除三级缓存),而非在bean对象创建后就无脑的进行缓存。即只有在真正需要读取缓存内容的时候才进行缓存,即将缓存的动作延迟到了需要读取缓存的时候。这样,就极大了减少了因为缓存给系统资源带来的压力。
并且,因为有三级缓存的存在,还可以将代理对象的创建和对象缓存的职责解耦,代理逻辑也更加清晰明了。

六、三级缓存的整体流程

在bean对象创建时,会将bean的工厂对象缓存到三级缓存里,当有其他bean对象首次需要引用该bean对象时,会首先检查一级缓存是否存在完整的bean对象,若有则获取并返回;否则检查二级缓存是否存在尚未进行属性注入的早期bean对象,若有则获取并返回;否则检查三级缓存是否存在bean的工厂对象,若有则判断如果是代理对象则创建代理对象并放到二级缓存,否则通过反射获取原始对象放到二级缓存,并清除三级缓存。当后续构建出完整的bean对象,则会将其缓存到一级缓存并将二级缓存清除。当该bean对象在系统中没有存在的必要了;或者构成bean对象的基本信息有变化,需要根据变化后的基本信息来重新构建时,就需要清除该一级缓存了。

存在
不存在
存在
不存在
存在
无存在必要
基本信息变化
开始
创建Bean对象
缓存工厂对象到三级缓存
其他Bean引用该Bean
一级缓存检查
获取并返回完整Bean对象
二级缓存检查
获取并返回早期Bean对象
三级缓存检查
是否代理对象
创建代理对象
通过反射获取原始对象
放入二级缓存
清除三级缓存
结束
构建完整Bean对象
缓存到一级缓存
清除二级缓存
检查是否需要清除一级缓存
清除一级缓存
结束

这个流程图详细描述了Bean对象的创建、引用和缓存管理过程。当Bean对象被创建时,其工厂对象会被缓存到三级缓存。当其他Bean对象需要引用这个Bean时,会按照一级缓存、二级缓存和三级缓存的顺序进行检查,并根据情况获取Bean对象或创建代理对象。一旦构建出完整的Bean对象,它会被缓存到一级缓存,并且二级缓存会被清除。如果Bean对象不再需要或其基本信息发生变化,一级缓存也会被清除。

七、总结

一级缓存(缓存完整的bean对象),无法解决循环依赖问题,而且存在资源浪费;
二级缓存(缓存尚未进行属性注入的早期bean对象),可以解决循环依赖问题,但是存在资源浪费,而且存在当涉及代理对象时对象创建和对象缓存的职责耦合的复杂度问题;
三级缓存(缓存构建bean的工厂对象),不仅可以解决循环依赖问题,而且通过将缓存的动作延迟到了需要读取缓存的时候,极大减少了因缓存给系统资源带来的压力。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylanioucn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值