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通过临时借用半成品钥匙的方式,让两个互相依赖的房子都能顺利盖完!

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

相关推荐
xiaoye370814 小时前
Spring 中高级面试题
spring·面试
枫叶落雨22214 小时前
ShardingSphere 介绍
java
花花鱼14 小时前
Spring Security 与 Spring MVC
java·spring·mvc
言慢行善15 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星15 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟15 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z15 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可15 小时前
Java 中的实现类是什么
java·开发语言
He少年15 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新15 小时前
myeclipse的pojie
java·ide·myeclipse