Spring三级缓存通俗易懂讲解

Spring三级缓存通俗易懂讲解

一、用盖房子的比喻理解三级缓存

想象一下我们要盖两栋房子:A房B房,但是有个特殊要求:

  • A房需要B房的钥匙才能装修
  • B房需要A房的钥匙才能装修
  • 这就是循环依赖

Spring的三级缓存就像是三个不同阶段的钥匙保管箱

1. 一级缓存:成品房钥匙箱(singletonObjects)

  • 作用 :存放完全装修好的房子的钥匙
  • 特点:房子已经可以入住了,所有设施都齐全
  • 比喻:房产证已经办好,可以随时交房

2. 二级缓存:半成品房钥匙箱(earlySingletonObjects)

  • 作用 :存放正在装修的房子的临时钥匙
  • 特点:房子框架已经搭好,但内部装修还没完成
  • 比喻:房子主体结构完成,可以临时参观但不能入住

3. 三级缓存:施工队联系方式箱(singletonFactories)

  • 作用 :存放施工队的联系方式,可以随时联系他们继续装修
  • 特点:知道怎么把毛坯房变成精装修房
  • 比喻:建筑公司的项目经理电话

二、具体盖房过程(循环依赖解决)

场景:A房和B房互相需要对方的钥匙

java 复制代码
// A房需要B房的钥匙
@Service
public class HouseA {
    private HouseB houseB;  // 需要B房的钥匙
    
    public void setHouseB(HouseB houseB) {
        this.houseB = houseB;
    }
}

// B房需要A房的钥匙  
@Service
public class HouseB {
    private HouseA houseA;  // 需要A房的钥匙
    
    public void setHouseA(HouseA houseA) {
        this.houseA = houseA;
    }
}

盖房步骤详解:

第1步:开始盖A房
  • Spring说:"我要盖A房了!"
  • 先把A房施工队联系方式 存到三级缓存(施工队联系方式箱)
  • 现在A房还是个毛坯房,只有框架
java 复制代码
// Spring内部操作:
三级缓存.put("houseA", () -> {
    // 这个lambda就是施工队,知道怎么装修A房
    return 装修A房();
});
第2步:A房装修需要B房钥匙
  • A房施工队说:"我需要B房的钥匙才能继续装修A房"
  • Spring去检查:B房盖好了吗?
第3步:开始盖B房
  • Spring发现B房还没开始盖,于是:"我要盖B房了!"
  • B房施工队联系方式 存到三级缓存
  • B房现在也是毛坯房
第4步:B房装修需要A房钥匙
  • B房施工队说:"我需要A房的钥匙才能装修B房"
  • Spring去检查A房状态
第5步:关键操作 - 临时借用A房钥匙
  • Spring发现A房在三级缓存有施工队联系方式
  • 联系A房施工队:"先给我一个临时的A房钥匙"
  • 施工队快速装修出一个临时可用的A房(早期引用)
  • 把这个临时A房钥匙存到二级缓存(半成品房钥匙箱)
java 复制代码
// Spring内部操作:
Object earlyA = 三级缓存.get("houseA").getObject();  // 调用施工队
二级缓存.put("houseA", earlyA);  // 临时钥匙存到二级缓存
第6步:B房继续装修
  • Spring把临时A房钥匙交给B房施工队
  • B房施工队说:"好的,我有A房钥匙了,可以继续装修B房了"
  • B房装修完成,钥匙存到一级缓存(成品房钥匙箱)
第7步:A房继续装修
  • Spring说:"B房已经盖好了,这是B房的钥匙"
  • A房施工队拿到B房钥匙,继续完成A房装修
  • A房装修完成,钥匙也存到一级缓存
第8步:清理临时钥匙
  • 从二级缓存和三级缓存中移除A房和B房的临时信息
  • 现在一级缓存中有两把完整的钥匙:A房和B房

三、代码层面的具体流程

三级缓存在Spring中的实际代码

java 复制代码
// Spring内部的三个缓存Map(简化版)
public class DefaultSingletonBeanRegistry {
    
    // 一级缓存:成品Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 二级缓存:早期引用(半成品)
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
    // 三级缓存:Bean工厂(施工队)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}

具体解决流程代码模拟

java 复制代码
// 模拟Spring解决循环依赖的过程
public class SpringCacheDemo {
    
    public static void main(String[] args) {
        // 模拟三级缓存
        Map<String, Object> 一级缓存 = new HashMap<>();  // 成品房
        Map<String, Object> 二级缓存 = new HashMap<>();  // 半成品房
        Map<String, Supplier<Object>> 三级缓存 = new HashMap<>();  // 施工队
        
        System.out.println("=== 开始创建A房 ===");
        
        // 第1步:A房开始施工,记录施工队
        三级缓存.put("A房", () -> {
            System.out.println("  A房施工队:我正在装修A房...");
            Object a房实例 = new Object();  // 创建A房实例
            System.out.println("  A房施工队:A房装修完成!");
            return a房实例;
        });
        System.out.println("  A房施工队联系方式已记录到三级缓存");
        
        System.out.println("\n=== A房需要B房钥匙 ===");
        
        // 第2步:检查B房状态
        if (!一级缓存.containsKey("B房")) {
            System.out.println("  B房还没盖,先盖B房");
            
            // 第3步:B房开始施工
            三级缓存.put("B房", () -> {
                System.out.println("  B房施工队:我正在装修B房...");
                Object b房实例 = new Object();
                System.out.println("  B房施工队:B房装修完成!");
                return b房实例;
            });
            System.out.println("  B房施工队联系方式已记录到三级缓存");
            
            System.out.println("\n=== B房需要A房钥匙 ===");
            
            // 第4步:B房需要A房钥匙,检查A房状态
            if (三级缓存.containsKey("A房")) {
                System.out.println("  A房有施工队,先要个临时钥匙");
                
                // 第5步:获取A房临时钥匙
                Supplier<Object> a房施工队 = 三级缓存.get("A房");
                Object 临时A房 = a房施工队.get();  // 快速装修临时A房
                二级缓存.put("A房", 临时A房);
                System.out.println("  A房临时钥匙已存到二级缓存");
                
                // 第6步:B房继续施工
                Supplier<Object> b房施工队 = 三级缓存.get("B房");
                Object 成品B房 = b房施工队.get();
                一级缓存.put("B房", 成品B房);
                System.out.println("  B房成品钥匙已存到一级缓存");
                
                // 第7步:A房继续施工(现在有B房钥匙了)
                a房施工队 = 三级缓存.get("A房");
                Object 成品A房 = a房施工队.get();
                一级缓存.put("A房", 成品A房);
                System.out.println("  A房成品钥匙已存到一级缓存");
                
                // 第8步:清理缓存
                二级缓存.remove("A房");
                三级缓存.remove("A房");
                三级缓存.remove("B房");
                System.out.println("  临时缓存已清理");
            }
        }
        
        System.out.println("\n=== 最终结果 ===");
        System.out.println("一级缓存(成品房):" + 一级缓存.keySet());
        System.out.println("二级缓存(半成品):" + 二级缓存.keySet());
        System.out.println("三级缓存(施工队):" + 三级缓存.keySet());
    }
}

四、为什么需要三级缓存?

如果只有两级缓存会怎样?

假设我们只有一级缓存(成品房)二级缓存(施工队)

  1. A房开始施工 → 施工队存到二级缓存
  2. A房需要B房钥匙
  3. B房开始施工 → 施工队存到二级缓存
  4. B房需要A房钥匙
  5. 从二级缓存找到A房施工队
  6. 问题:如果直接调用施工队,A房会被完整创建两次!

三级缓存的好处:

  1. 避免重复创建:通过二级缓存存放早期引用,确保同一个Bean只被创建一次
  2. 支持AOP代理:如果需要AOP代理,三级缓存可以确保代理对象的一致性
  3. 性能优化:减少不必要的对象创建和初始化

五、实际Spring源码中的关键方法

getSingleton() 方法(找钥匙)

java 复制代码
// Spring源码简化版
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 第一级查找:成品房钥匙箱
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 第二级查找:半成品房钥匙箱
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 第三级查找:施工队联系方式箱
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 联系施工队,获取临时钥匙
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

addSingletonFactory() 方法(记录施工队)

java 复制代码
// 记录施工队联系方式
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 存到三级缓存(施工队联系方式箱)
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
        }
    }
}

六、总结

记住这个简单的比喻:

  • 一级缓存 = 成品房钥匙箱(可以入住的房子)
  • 二级缓存 = 半成品房钥匙箱(正在装修的房子)
  • 三级缓存 = 施工队联系方式箱(知道怎么装修房子的施工队)

当遇到循环依赖时,Spring通过临时借用半成品钥匙的方式,让两个互相依赖的房子都能顺利盖完!

这样理解是不是就清楚多了?

相关推荐
披着羊皮不是狼3 小时前
多用户博客系统搭建(1):表设计+登录注册接口
java·开发语言·springboot
WX-bisheyuange6 小时前
基于Spring Boot的教师个人成果管理系统的设计与实现
java·spring boot·后端
xunyan62347 小时前
面向对象(上)-封装性的引入
java·开发语言
脸大是真的好~7 小时前
黑马JAVAWeb-05 JDBC入门-预编译SQL-Mybatis入门-Mybatis日志输出-数据库连接池-增删改查-XML映射配置
java
还算善良_7 小时前
XML签名
xml·java·开发语言
梅梅绵绵冰7 小时前
xml方式实现AOP
xml·java·开发语言
桦说编程8 小时前
Guava 迭代器增强类介绍
java·后端·设计模式
资深web全栈开发9 小时前
如何正确使用缓存:常见陷阱与最佳实践
redis·缓存·golang
235169 小时前
【JVM】Java为啥能跨平台?JDK/JRE/JVM的关系?
java·开发语言·jvm·spring boot·后端·spring·职场和发展