Spring为什么需要三级缓存
目录
引言
回答
思考
为什么bean工厂要放在三级缓存里面
二级缓存与三级缓存的区别
为什么不把ObjectFactory直接放到二级缓存?
总结
测试代码(便于理解)
结尾
引言
Spring为什么需要三级缓存,二级是不是已经够了,三级缓存到底是用来干嘛的呢?最近面试被问到过这个问题,我也在短视频网站上刷到了这个问题,今天特地学习了一下,接下来完全是我自己的一些个人见解,如果有错误,还请大佬们指正!!
回答
思考
问到了当然要说一定要说二级缓存肯定是不够的,至于为什么不够,你就说这是Spring创作团队自己设计的(开个玩笑!!)。首先你要知道Spring设计缓存主要是为了解决循环依赖的问题,之后我们就要知道每一层缓存都存放了一些什么东西。
- 一级缓存:存放的是已经初始化好的bean
- 二级缓存:存放的是早期暴露的对象(已经实例化但未完全初始化的bean)
- 三级缓存:存放bean工厂对象,他们可以生成bean的早期引用,用于解决循环依赖
知道了每一个缓存是放什么的了,你就应该想到,当只有二级缓存时,是能解决一部分循环依赖的问题的,因为二级缓存里面也是有东西的,但是!!二级缓存存放的是一个早期暴露的bean,所以你可以理解为是一个静态对象,当循环依赖的时候如果需要动态代理对象,或者是切面对象,那么循环依赖问题就无法得到解决,这个时候三级缓存就产生了,它通过工厂方法可以动态生成Bean对象(包括AOP代理对象),这样即使在循环依赖时,也能确保注入的是最终的代理对象,既解决了循环依赖又保证了AOP等功能的正确性。这就是为什么需要三级缓存的原因。 这里理解了应该就能对面试官解释为什么需要三级缓存了。
为什么bean工厂要放在三级缓存里面
那么问题又又又来了 ,既然早就知道二级缓存里面是个静态对象,为什么不能在二级缓存里面就同时生成bean工厂,从而减少缓存呢?
二级缓存与三级缓存的区别
首先,我们要理解他们俩的本质区别
- 二级缓存:存的是“已经实例化但尚未初始化完成的Bean对象”(可能原始对象,也可能代理对象)。
- 三级缓存:存的是“生成早期Bean对象的工厂”,可以动态决定返回原始对象还是代理对象。
为什么不把ObjectFactory直接放到二级缓存?
咱们写代码的时候都讲究逻辑清晰,分层架构设计,缓存究其根本也是代码啊,也要讲究逻辑清晰这些啊。
- 二级缓存是“对象”级,三级缓存是“工厂”级
- 二级缓存的设计就是直接存对象,一旦放进去,这个Bean的引用就“定死了”,下次循环依赖只会返回同一个对象(原始或已被代理)。
- 如果把ObjectFactory放进二级缓存,每次get都需要判断是返回原始对象还是代理对象,逻辑会变得混乱,而且二级缓存的“已实例化对象”语义会被破坏。
- 生命周期与职责分明
- 三级缓存是“工厂”,它的职责是延迟决定提供什么对象(原始还是代理),只在需要提前暴露时调用。
- 二级缓存一旦存放对象,就代表这个对象已经被“提前暴露”,生命周期更靠后。
- Spring这样设计,可以明确区分:
- 还没决定要暴露什么对象 → 放工厂到三级缓存
- 已经确定要暴露这个对象了 → 放对象到二级缓存
- 释放顺序和引用安全
- 当一个Bean代理完成后,Spring会把代理对象从三级缓存的工厂生成出来,放到二级缓存,然后清理掉三级缓存里的工厂,从而节省内存且不再暴露原始对象引用的风险。
- 如果ObjectFactory和对象混在同一个缓存里,清理和管理会很混乱,容易出现引用泄漏或AOP失效等问题。
总结
Spring将ObjectFactory和对象分别放在三级缓存和二级缓存,目的是职责单一、生命周期清晰、引用安全,避免对象和工厂混用导致的管理混乱和代理失效。二级缓存只存对象,三级缓存只存工厂,这样设计更加健壮和易于维护。
测试代码(便于理解)
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;interface Bean {}class A implements Bean {B b;public void setB(B b) { this.b = b; }public void say() { System.out.println("A says, B is: " + b); }
}class B implements Bean {A a;public void setA(A a) { this.a = a; }public void say() { System.out.println("B says, A is: " + a); }
}// 简单模拟AOP代理
class AopProxyB extends B {private final B target;public AopProxyB(B target) { this.target = target; }public void say() {System.out.println("AOP Before...");target.say();System.out.println("AOP After...");}
}public class SpringThreeLevelCacheDemo {// 一级缓存:成品对象private static final Map<String, Bean> singletonObjects = new HashMap<>();// 二级缓存:早期暴露对象private static final Map<String, Bean> earlySingletonObjects = new HashMap<>();// 三级缓存:ObjectFactory(可生成代理对象)private static final Map<String, Supplier<Bean>> singletonFactories = new HashMap<>();public static void main(String[] args) {// 注册三级缓存工厂,模拟Spring提前暴露singletonFactories.put("b", () -> {// 模拟AOP代理逻辑B rawB = (B) earlySingletonObjects.get("b");if (rawB != null) {return new AopProxyB(rawB); // 返回代理对象}return null;});// 创建A和B并互相注入// 1. 创建B对象B b = new B();earlySingletonObjects.put("b", b);// 2. 创建A对象A a = new A();// 这里A依赖B,Spring会先去缓存里找B// 假如B需要AOP代理,应该返回代理对象// 先尝试从一级缓存拿BBean bForA = singletonObjects.get("b");if (bForA == null) {// 其次尝试从二级缓存拿BbForA = earlySingletonObjects.get("b");if (bForA == null) {// 最后用三级缓存的工厂创建bForA = singletonFactories.get("b").get();earlySingletonObjects.put("b", bForA);singletonFactories.remove("b");}}a.setB((B) bForA);// 3. B依赖Ab.setA(a);// 4. 完成B的代理,放入一级缓存B aopB = (B) singletonFactories.get("b").get();singletonObjects.put("b", aopB != null ? aopB : b);singletonObjects.put("a", a);// 测试调用((A) singletonObjects.get("a")).say();((B) singletonObjects.get("b")).say();}
}
结尾
其实大家如果想要了解更多的知识点,就可以去看看Spring的源码,虽然源码确实枯燥无味,现在这个时代又是一个AI时代,不会就去问AI,但AI终究是人设计的,有什么我们还是应该了解一下底层逻辑
最后,秋招已经开始了,各位拿到心仪的offer了吗,如果有大佬看到这篇文章,能否在评论区留下内推码呢,祝大家秋招都能拿到心仪的offer!!!