Spring 三级缓存 ---- 简单明了豆包版

Spring 二级缓存即可解决循环依赖问题,引入三级缓存是因为要解决 bean 的 AOP 代理问题。

一级缓存 singletonObjects: 完全初始化好的成品 Bean,包括代理类,它里面的引用对象也已经设置好;

二级缓存 earlySingletonObjects:早期原始对象或早期代理对象,即已经 new 出来,但它里面的属性为 null;

三级缓存 singletonFactories:存储 ObjectFactory<?> 工厂对象,负责制造 bean 。

在Java 中,可以通过 setter 注入解决循环依赖问题,但构造器注入不能解决循环依赖问题。二级缓存就是利用这个方式解决普通循环依赖。写代码的时候有些人推荐使用构造器注入,这样如果代码出现循环依赖就会马上抛出异常。

下面的代码 A和 B 可以通过 set 的方式解决,但构造器不行,因为Spring 中的 bean 都是单例的,所以 通过 C c=new C(new D()); D d=new D(new C()); 得到的 c 和 d 不是相互引用的,在 Spring 中不行。

复制代码
class A {
    private B b;

    public void setB(B b) {
        this.b = b;
    }
}

class B {
    private A a;

    public void setA(A a) {
        this.a = a;
    }
}

class C {
    private D d;

    public C(D d) {
        this.d = d;
    }
}

class D {
    private C c;

    public D(C c) {
        this.c = c;
    }
}

public class Test {

    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.setB(b);
        b.setA(a); 
    }
}

一级缓存就是一个已经初始化好的 Spring 容器,一个巨大的 HashMap,我们可以通过 bean 的名称获取到该类型的唯一对象。二级缓存是这样解决循环依赖的,先 new A() 出来,此时它是个半成品,放入二级缓存,发现 A 依赖B,然后去创建 B,初始化B的属性时发现 B 要获取 A 的实例,B发现一级缓存没有 ,就去二级缓存找,正好找到了 A 的实例化对象,设置,然后 B 就可以放入一级缓存,而A 此时正好也在一级缓存找到 B 的实例,然后把 A 也移入到一级缓存。完美解决。

但二级缓存无法解决 AOP 代理问题,当 A 是个代理对象时:A new 出来后放在二级缓存,发现它依赖 B,B 也 new 出来了,要找 A 的实例,结果在二级缓存找到了 A的原始实例,b 设置了它的 A 属性,然后A 也设置好了它的 b 属性,此时 A 的属性初始化完成,要被代理程 A_proxy,然后 一级缓存和 Spring 容器里就只有 A_proxy 而没有 A的实例,而 B 对象里的 A 属性是普通的A对象,不是 A_proxy 对象,b.a 和 A_proxy 对象的内存地址不一致,就出现 bug。

有了三级缓存后:A 实例化 new 出来后不把对象放到二级缓存,而是把 A的 ObjectFactory 放到三级缓存,然后发现A 依赖 B,然后去new B,B 实例化后要初始化属性,然后在一二级缓存都找不到A,在三级缓存找到了A的 ObjectFactory ,然后B 执行 A 的工厂方法,得到A的代理类 A_proxy,此时 A_proxy 只是实例化但并没有属性初始化,然后把 A_proxy 放到二级缓存,设置 b.a=A_proxy_obj ,B 属性初始化完成,然后 A 继续初始化,从 二级缓存拿到 A_proxy_obj,然后初始化 A 的属性,最终放到一级缓存和Spring 容器里。最终

A_proxy.b 和b是一个对象,b.a 和 A_proxy 也是一个对象。

三级缓存就是为了延迟实例化,即便 A 和 B 是循环依赖且都要生成代理类,三级缓存也能解决。

复制代码
        实例化 A → 把 () -> createProxy(A) 放进三级缓存

        实例化 B → 把 () -> createProxy(B) 放进三级缓存

        A 填充 B → 去拿 B → 触发 B 工厂生成B 代理 → 存入二级缓存

        B 填充 A → 去拿 A → 触发 A 工厂生成A 代理 → 存入二级缓存

        A、B 初始化完成,把二级里的同一个代理放进一级缓存
相关推荐
张涛酱1074562 小时前
Agent Skills 深入解析:构建可插拔的智能体知识体系
spring·设计模式·ai编程
隐退山林2 小时前
JavaEE进阶:导读&SpringBoot快速上手
java·spring boot·java-ee
送秋三十五2 小时前
Spring 源码---------Spring Core
java·数据库·spring
悟空码字2 小时前
SpringBoot + 微信支付实现“扫码开门,取货自动扣款”售货柜
java·spring boot·后端
沐雪轻挽萤2 小时前
1. C++17新特性-序章
java·c++·算法
殷紫川2 小时前
Spring AI 整合火山引擎豆包向量库搭建企业知识库:我踩过的 10 个致命坑与终极解决方案
java·ai编程
呆呆在发呆.3 小时前
JavaEE初阶
java·jvm·网络协议·学习·udp·java-ee·tcp
算.子3 小时前
【Spring 实战】Spring AI 进阶专题:Token 成本优化与 Structured Output
java·人工智能·spring
Gopher_HBo3 小时前
ReentrantReadWriteLock源码讲解
java·后端