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 初始化完成,把二级里的同一个代理放进一级缓存
相关推荐
xxwl58510 小时前
数据库后端接口测试报告
spring boot·mysql·tomcat
令狐前生10 小时前
Intellij IDEA 2025 破解安装
java·ide·intellij-idea
zzzzzz31010 小时前
别让大模型直接碰业务:我在 Spring Boot 里给 AI 操作加了一道“可拒绝的闸门”
人工智能·spring boot·spring
xqqxqxxq10 小时前
MySQL 数据类型笔记
数据库·笔记·mysql
数聚天成DeepSData10 小时前
数聚天成 DeepSData 数据价值落地实战指南
java·maven·devops
小小小米粒10 小时前
阿姆达尔定律(Amdahl‘s Law)
java·开发语言
知彼解己10 小时前
Java 版本演进
java·开发语言·spring boot
Logintern0911 小时前
理解MySQL数据库的“事务与锁”
数据库·mysql
极客先躯11 小时前
高级java每日一道面试题-2026年05月11日-实战篇[Docker]-如何容器化金融产品推荐系统?
java·运维·docker·容器·金融·高级面试·金融产品推荐系统
朱容zr33313311 小时前
为什么推荐使用自增主键?使用UUID作为主键的优缺点是什么?
java·运维·数据库·后端·mysql·面试·性能优化