Spring 三级缓存解析
首先需要注意,三级缓存即是三层缓存,只不过Spring框架在设计时对每一层缓存都进行了单一职责原则的划分。换句话说,三级缓存不仅仅是用于简单存储数据的,而是有明确的准入标准和设计概念:
一个类创建为一个对象的过程通常分为三个步骤:实例化、属性填充、初始化。
例如,对于类 A
,其中的字段 b
定义为 String b = "dacongming"
。当执行 A a = new A();
时:
- 实例化:会先为这个对象分配内存,此时
a
这个引用的地址已经确定,但字段b
的值为null
。 - 属性填充:此时会为
A
类中的字段赋值,比如b
的值会变为"dacongming"
。 - 初始化:对
a
对象进行一些初始化处理,比如如果你实现了初始化方法,那么此时就会执行这个方法,这样一个对象就创建完毕了。
Spring 如何通过三级缓存解决循环依赖
Spring 解决循环依赖的三级缓存机制可能大家已经有所耳闻了:
- singletonObjects:一级缓存,存储已经初始化完成的单例Bean。
- earlySingletonObjects:二级缓存,存储尚未完成初始化的Bean。
- singletonFactories:三级缓存,存储提前暴露的单例工厂。二级缓存中存储的Bean就是从这个工厂中获取到的对象。
当从缓存中获取对象时,Spring 按照以下顺序进行查询:
- 首先从一级缓存查询;
- 如果未查到,再从二级缓存查询;
- 如果二级缓存中也未查到,再从三级缓存查询;
- 如果三级缓存中仍未找到,则创建该对象。
Spring 循环依赖的流程解析
以 A 和 B 两个类相互依赖为例,Spring 的解决流程如下图所示:
我们可以看到,A 对象是完整使用了三级缓存的。在 A 对象实例化完成后,它会主动将对应的BeanFactory
放入三级缓存中。那么,为什么要将BeanFactory
放入三级缓存,而不是直接将 A 的原始对象放入二级缓存呢?这是因为 A 可能需要生成代理对象。
在第2步中,BeanFactory
其实是一个回调函数:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))
当你从BeanFactory
中获取 A 的对象时,相当于触发了这个回调函数
上面的匿名函数()->getEarlyBeanReference(beanName, mbd, bean)
就是回调函数,它的第三个入参bean
是 A 的原始对象。这个函数的逻辑是,如果 A 需要生成代理对象,那么会对 A 的原始对象进行包装,并返回 A 的代理对象。
正常情况下,代理对象的创建是在原始对象初始化完毕之后进行的。但这里 A 的原始对象还未创建完成,就提前创建了 A 的代理对象。这是因为 A 被 B 引用了,而 A 后续需要以代理的方式存在于 Spring 上下文中,所以要提前创建 A 的代理对象并将其引用提供给 B,这样 B 在使用 a 属性时,使用的才是 A 的代理对象。
因此,A 的代理对象相当于提前暴露了,它内部包含了对 A 原始对象的引用。当 A 的原始对象初始化完毕时,A 的代理对象也就完成了初始化。
如果 A 不需要代理对象,那么将直接返回 A 的原始对象。无论返回的是代理对象还是原始对象,都会从三级缓存中移除,并放入二级缓存。因此,如果没有代理需求,完全可以跳过三级缓存,直接将原始对象放入二级缓存中,这样二级缓存中存储的都是未完全初始化的对象,而一级缓存则存储的是已经彻底初始化完成、可以直接使用的对象。