Spring三级缓存
Posted ctrl-00后
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring三级缓存相关的知识,希望对你有一定的参考价值。
什么是三级缓存
在 DefaultSingletonBeanRegistry
类(都说Spring是个大的bean工厂,那这个类就是工厂的大仓库,存放各种生产好,或生产中的bean)中有下面三个属性
为什么需要三级缓存
在这里我们需要一些前置的知识————Spring创建bean的流程中4个最基本的方法
getBean--------从bean工厂获取bean
createBeanInstance--------创建bean实例,其实就是通过反射生成bean
populateBean--------填充bean的属性(因为反射生成的bean就是个空壳子,所有属性都为空,需要在这里填充)
initializeBean--------执行bean的初始换方法,以及通过bean的后置处理器对bean进行增强(这里我们关心的是bean后置处理对bean做了怎样的增强)
假设只有一级缓存,但两个Bean之间相互引用
我提前写好A和B两个类
程序启动,Spring会调用getBean方法获取类型为A的bean,发现仓库没有,就会调用createBeanInstance方法通过反射调用A类的空参构造器创建A对象,但这时的A对象的属性值都为空。
既然A对象属性为空那么我们当然要填充bean,接下来就是调用populateBean方法,这时候发现A类中有个B类的属性,并且是@Autowired注解修饰,那我们就要看看bean仓库里面有没有B的bean
当然B的bean现在是没有的,那么就要实例化,和 A 一样,B也同样进行createBeanInstance方法,而且 B 类也有个 A 类的属性需要自动装配,那B同样要执行 populateBean 方法。
问题来了,B在进行填充属性的时候,又去调用getBean方法从仓库中获取 A 的bean,但发现没有。
我之前假设了,如果只有一级缓存,那这不就成了死循环了么?
所以需要二级缓存,在A一开始被实例化时,在还没填充bean之前可以将bean放入二级缓存,这样B在填充bean时就可以获取到半成品的A这个bean,紧接着A就可以获得到成品的B这个bean(注意,这里的步骤都是我们的假想,Spring不是这么做的,但原理相似,毕竟我们还有个三级缓存要说)
小结:二级缓存解决循环依赖问题
如果bean要被动态代理
在此之前我们还得搞清楚一个问题,什么时候进行的动态代理?
A 和 B 两个对象假设都要进行动态代理
请问这时,A这个bean里面引用的B这个bean应该是代理对象还是普通对象呢?
对,A 和 B 的bean都应该引用对象的代理对象,只有这样,才能在调用它们方法的时候进行增强(代理对象可以在原本要执行的方法前后和异常处等进行一些别的操作,比如事务、日志、缓存等)。
OK,那我们梳理一下,如果A先被 getBean 调用,刚开始会去单例池获取,那肯定是没有啊。那么就自己去通过反射创建,这个之前说过,但目前的 A 是个半成品的bean,不能放入提前引用的二级缓存,假如要将该bean进行代理呢!那能不能直接代理,再放入,实现上我个人认为可以,但没这个必要,毕竟浪费空间,执行效率也会降低。
所以又回到问题开始的地方,什么时候生成动态代理?
其实是2个时机
- 在getBean时当发现当前bean被其他bean引用
- 在普通bean被初始化完成后
一个个来说,为什么这么做,还是以 A 和 B 举例。当A 生成半成品bean时,发现自己要填充 B 这个bean,紧接着调用 getBean(“b”),B 也生成半成品bean,也要填充,此时getBean(“a”)时,A发现自己还没填充完成,就被别人引用了,不能直接返回普通bean,先得看看自己需不需要被代理,发现需要,则直接返回代理对象。
当 B 获得 A 的代理对象后,执行初始化方法,之前说过,初始化方法会做两件事。
- 执行自定义的初始化代码
- 执行bean的后置处理器方法
在执行bean的后置处理器方法时,如果发现当前bean要被代理,则返回的是代理对象,最后 A 也就能获得 B 的代理对象,依赖循环和动态代理就此结束。
神马?这就结束了?三级缓存呢?
三级缓存它来了
在写三级缓存之前,我们先思考一个问题,我们的spring是先存放一级缓存还是二级还是三级?
想知道这个问题,我们先打个断点
这里说一下,他们的put方法只被调用一次,然后我们来看看这些加了条件的断点停在哪?
是它,和你想的一样么?
那我们再看看它这里面存的 singletonFacotry 究竟是个啥
半成品bean说明一点,就是 A 已经经过了反射,但为什么不放入二级缓存呢?还是之前的那个问题,在被其他bean引用时我们必须保证当前被引用的bean如果是需要被代理的,要先进行bean的替换。
是不是这样的呢?下面我们放开断点,调到下一个放入二级缓存的断点中
顺便说一下,这是Spring执行的哪一步呢
至此,A的代理替换完成,那 B 的代理在哪呢?
执行代码之前
执行代码之后
总结
各级缓存的作用
一级缓存:存放单例
二级缓存:存放提前引用
三级缓存:存放单例工厂
代理对象生成时机
- 被其他bean引用时
- 实例化bean完成之后
三级缓存解决什么问题
- 循环依赖
- 动态代理bean的替换
以上是关于Spring三级缓存的主要内容,如果未能解决你的问题,请参考以下文章