共计 2572 个字符,预计需要花费 7 分钟才能阅读完成。
谈谈我本人了解的 Spring 为什么肯定要用三级缓存
问题和我了解的论断
二级缓存能解决循环依赖吗?为什么肯定要三级缓存?
能,为了在尽可能放弃原有的 bean 创立流程不扭转(Spring 的 bean 创立标准)的前提下解决代理循环依赖
集体了解的三级缓存由来
咱们不顺着 spring 的代码说。先来看看 bean 的初始化流程有一个整体标准步骤,大抵能够这么形容(其实还存在初始化前置、属性赋值前置等等步骤,但这里不须要关怀所以不列出):
1、实例化
2、属性赋值
3、初始化
4、初始化实现后的后置操作,而 AOP 的代理包装对象就在这一步实现
遵循这一大前提步骤,如果咱们本人来做这个 bean 创立流程,首先必定要有一个容器,所以至多一层缓存是须要的,那咱们就先用一层缓存试试,如果只有一个缓存容器,容器的实质就是一个缓存,咱们简略称这个缓存为成品缓存,用来寄存最终成品 bean 实例
场景一、A 依赖 B
1、A 实例化
2、A 属性赋值,发现须要 B
3、B 实例化,属性赋值,B 初始化,B 放入成品缓存
4、A 拿到成品 B,间接赋值
5、A 初始化
6、A 放入成品缓存
如上,简略的创立流程 1 个缓存能够解决
场景二、A 依赖 B,B 依赖 A
1、A 实例化
2、A 属性赋值,发现须要 B
3、B 实例化
4、B 属性赋值,发现须要 A,但从成品缓存没拿到 A,因为这时候 A 还没创立实现不能放入成品缓存,导致 B 实例创立失败
如上,只有一个缓存,遇到循环依赖问题就没辙了
那怎么办?
这里有一个核心思想解决思路就是,提前裸露 ,搞两个缓存,
缓存变为:
- 一个存最终成品(成品缓存)
- 一个存半成品(半成品缓存)
这时候流程如下:
1、A 实例化,并将实例化的 A 间接放入半成品缓存
2、A 属性赋值,发现须要 B
3、B 实例化,并将实例化的 B 间接放入半成品缓存
4、B 属性赋值,发现须要 A,间接从成品缓存拿没拿到,再从半成品缓存拿,拿到了长期 A
5、B 初始化
6、B 初始化完,执行后置操作,这里能够把成品 B 放入成品缓存,并将半成品缓存的 B 删除
7、A 拿到残缺 B,间接赋值实现
8、A 初始化
9、A 初始化完,执行后置操作,成品 A 放入成品缓存,并将半成品缓存的 A 删除
如上,简略的循环依赖问题 2 个缓存能够解决
场景三、A 依赖 B,B 依赖 A,A 须要 AOP 代理
1、A 实例化,并将实例化的 A 间接放入半成品缓存
2、A 属性赋值,发现须要 B
3、B 实例化,并将实例化的 B 间接放入半成品缓存
4、B 属性赋值,发现须要 A,间接从成品缓存拿没拿到,再从半成品缓存拿,拿到了长期 A
5、B 初始化
6、B 初始化完,执行后置操作,这里能够把成品 B 放入成品缓存,并将半成品缓存的 B 删除
7、A 拿到残缺 B,间接赋值实现
8、A 初始化
9、A 初始化完,执行后置操作,因为没有已存在代理包装 A1,将 A 包装成代理 A1,并将成品 A1 放入成品缓存,删除半成品缓存的 A
如上,如果用简略的在半成品缓存保留长期对象有一个很显著的问题,就是创立 B 的时候拿到的是 A 本体,但 A 须要被 AOP 代理,所以其实真正被外界援用的应该是最初的成品 A1,这两者不是同一个对象。
那要怎么办?
这里有另一个外围解决思路就是,不间接裸露对象自身而是裸露获取这个对象的工厂 ,这样每次要用到的时候间接取取工厂,而后工厂判断裸露的对象是否须要代理包装,需要的话就返回代理包装,不须要则返回一般的对象
缓存变为:
- 一个存最终成品(成品缓存)
- 一个存获取半成品的工厂(工厂缓存)
这时候流程如下:
1、A 实例化,并将能够获取实例的工厂 A 放入工厂缓存
2、A 属性赋值,发现须要 B
3、B 实例化,并将能够获取实例的工厂 B 放入工厂缓存
4、B 属性赋值,发现须要 A,间接从成品缓存拿没拿到,再从工厂缓存拿,拿到了工厂 A,工厂 A 返回时发现 A 须要被代理,所以返回代理 A1
5、B 初始化
6、B 初始化完,执行后置操作,把成品 B 放入成品缓存,并将工厂缓存的 B 删除
7、A 拿到残缺 B,间接赋值实现
8、A 初始化
9、A 初始化完,执行后置操作,因为任何一个缓存里都没有已存在的代理包装 A1,将 A 包装成代理 A2,并将成品 A1 放入成品缓存,删除工厂缓存的工厂 A
如上,很显著,工厂尽管解决了 B 援用不到 A 代理对象的问题,然而仍然无奈解决 A1 和 A2 不是同一个对象的问题
那要怎么办?
这里就须要引入再次最初一个缓存,那就是用来存工厂创立进去的长期对象的缓存
缓存变为:
- 一个存最终成品(成品缓存)
- 一个存工厂创立进去的长期变量(半成品缓存)
- 一个存获取半成品的工厂(工厂缓存)
这时候流程如下:
1、A 实例化,并将能够获取实例的工厂 A 放入工厂缓存
2、A 属性赋值,发现须要 B
3、B 实例化,并将能够获取实例的工厂 B 放入工厂缓存
4、B 属性赋值,发现须要 A,从工厂缓存拿,工厂 A 返回时发现 A 须要被代理,则生成代理 A1,并将 A1 放入半成品缓存
5、B 初始化
6、B 初始化完,执行后置操作,把成品 B 放入成品缓存,删除工厂缓存
7、A 拿到残缺 B,间接赋值实现
8、A 初始化
9、A 初始化完,执行后置操作,发现半成品缓存已存在代理包装 A1,所以不再从新包装代理,间接将 A1 放入成品缓存,并删除半成品缓存
至此,各种场景都失去了解决,但我一开始也说了,这一路下来的解决思路都是基于 Spring 创立 bean 的大流程前提顺着来的,如果不顺着这个前提流程,可不可以间接用 2 个缓存解决存在 AOP 的循环依赖问题呢?
能。
二级怎么解决 aop 场景下的循环依赖问题?
还是基于场景三
1、A 实例化,而后间接生成代理对象 A1,放入半成品缓存(这样即便前面有 C 也须要用 A,拿到的永远是一个对象,没有因为工厂会创立多个对象的问题)
2、A 属性赋值,发现须要 B
3、B 实例化,而后间接生成半成品,放入半成品缓存
4、B 属性赋值,发现须要 A,间接从半成品缓存拿到 A1,赋值实现
5、B 初始化
6、B 初始化完,执行后置操作,将半成品缓存中的 B 拿到成品缓存,删除半成品缓存的 B
7、A 失去残缺 B,赋值实现
8、A 初始化
9、A 初始化完,执行后置操作,发现半成品缓存已存在代理包装 A1,将半成品缓存中的 A1 放到成品缓存,删除半成品缓存
至此我集体认为,二级缓存能够解决所有问题,做成二级缓存须要将代理加强这一步放到实例化之后立马做。然而 Spring 的整体流程标准是先创建对象,再做加强操作,须要代理的毕竟是多数,所以 Spring 这里不会为了多数状况扭转整体流程,在保留大流程不变的状况下,他做了三级缓存(起因还是参照后面一步步遇到各种状况下的演变)