Spring 三级缓存机制(解决循环依赖)

news/2025/2/27 3:37:30

文章目录

    • 🔄 现实生活类比:开餐厅的过程
    • 💡 结合到 Spring 三级缓存
    • 🛠️ Spring 解决循环依赖的步骤
      • 1️⃣ Spring 开始创建 A
      • 2️⃣ Spring 开始创建 B
      • 3️⃣ B 创建完成后,回过头来继续创建 A
    • 📌 三级缓存的作用
    • ❓ 为什么不用两级缓存,而要三级缓存
    • 🔚 结论

🔄 现实生活类比:开餐厅的过程

假设你是一个餐厅老板,你有两个大厨 A 和 B,他们互相配合做菜:

  • A 需要 B 提供酱料
  • B 需要 A 提供食材
  • 但 A 和 B 都必须先开工,才能完成各自的任务。

如果他们都等对方的食材/酱料准备好,餐厅就会卡死,无法开张!(就像 Bean 创建时循环依赖)

于是,你(Spring 容器)设计了一个聪明的解决方案

  1. 先让 A 和 B 都开始工作,即使他们还没完全准备好。
  2. 让他们先把“半成品”存放在一个共享架子上,这样可以让对方提前拿到需要的东西。
  3. 等到他们各自的任务完成后,再正式交付最终产品。

这个 共享架子,就是 Spring 三级缓存的核心机制!🔍


💡 结合到 Spring 三级缓存

现实餐厅Spring 三级缓存
A 和 B 互相依赖,但必须同时开始工作A 依赖 B,B 依赖 A,必须同时创建
共享架子,存放半成品,方便对方使用二级缓存(earlySingletonObjects),存放半成品 Bean
如果需要特别处理(如食材加工),可以先放入工厂区三级缓存(singletonFactories),存放 Bean 工厂

🛠️ Spring 解决循环依赖的步骤

假设 A 和 B 互相依赖(Setter 方式),Spring 解决循环依赖的过程如下:

1️⃣ Spring 开始创建 A

  • 发现 A 依赖 B,于是暂停 A 的创建。
  • 先把 A 的“工厂”(可以用来创建 A 的工具)放入 singletonFactories (三级缓存

2️⃣ Spring 开始创建 B

  • 发现 B 依赖 A,Spring 先去缓存里找 A。
  • 此时 A 还没完全创建,但 Spring 可以从三级缓存里找到 A 的工厂,并提前创建 A 的半成品,然后存入 earlySingletonObjects (二级缓存
  • B 现在可以正常获取 A 了,继续完成 B 的创建。

3️⃣ B 创建完成后,回过头来继续创建 A

  • A 依赖的 B 现在已经创建好了,A 终于可以完成创建,并存入 singletonObjects(一级缓存)。
  • B 也被放入一级缓存

最终,A 和 B 都成功创建,没有死锁!


📌 三级缓存的作用

缓存作用何时使用
一级缓存(singletonObjects)存放完全初始化的 BeanBean 创建完成后
二级缓存(earlySingletonObjects)存放提前暴露的半成品 Bean解决循环依赖时(但不支持代理)
三级缓存(singletonFactories)存放Bean 创建工厂用于创建代理对象(如 AOP)

如果有 AOP 代理,Spring 会先从三级缓存拿出工厂,创建代理对象,再放入二级缓存,保证最终拿到的 Bean 是代理对象,而不是普通对象。


❓ 为什么不用两级缓存,而要三级缓存

如果只用二级缓存(不使用三级缓存),会有什么问题?

  • 无法支持 AOP 代理

    • 代理对象是在Bean 创建完后才生成的,如果没有三级缓存,Spring 就没办法在 Bean 创建前插手进行代理。
    • 三级缓存允许 Spring 在代理对象生成前,先存放一个“创建工厂”,让 Spring 可以决定是返回原始对象还是代理对象

🔚 结论

  1. Spring 通过三级缓存(singletonObjects、earlySingletonObjects、singletonFactories)来解决 Setter 方式的循环依赖。
  2. 一级缓存存放完全创建好的 Bean,二级缓存存放提前暴露的半成品,三级缓存存放 Bean 工厂(支持 AOP 代理)。
  3. 如果 Bean 需要 AOP 代理,Spring 先从三级缓存获取工厂,创建代理对象,然后存入二级缓存,最终返回代理对象。
  4. 构造器循环依赖无法通过三级缓存解决,因为 Spring 不能提前暴露半成品。

📌 总结一句话: Spring 通过共享“半成品”机制(三级缓存,让 A 和 B 在创建过程中互相提前看到对方,避免循环等待,从而成功创建!🎯


http://www.niftyadmin.cn/n/5869420.html

相关文章

Windows 图形显示驱动开发-WDDM 3.2-自动显示切换(四)

控制面板自我刷新 (PSR) ADS 功能使用 PSR 来避免切换过程中出现故障。 具体来说,使用 PSR1(全屏更新模式)时,GPU0 和 GPU1 无需协商使用哪种 PSR 模式。 即使在 PSR1 中,面板也需要支持以下的可选功能: …

前端浏览器开发中的浏览器兼容问题【持续更新】

目录 一、什么是浏览器兼容问题 二、JavaScript兼容问题及解决方案 2.1addEventListener与attachEvent的区别 2.2集合类对象问题 2.3自定义属性问题 2.4event.x与event.y问题 2.5window.location.href问题 2.6事件委托方法 三、CSS兼容问题及解决方案 3.1浏览器CSS样式初…

leetcode704------二分法查找有序数组中特定的值

目录 一、二分法的基本概念 二、二分法的基本步骤 三、迭代二分法查找有序数组中的特定值题目 3.1 题目介绍 3.2 求解思路 3.2.1 情况一:左闭右闭[left, right] 3.2.2 情况二:左闭右开[left, right) 四、二分法的时间复杂度与空间复杂度 4.1 …

【STM32】玩转IIC之驱动MPU6050及姿态解算

目录 前言 一.MPU6050模块介绍 1.1MPU6050简介 1.2 MPU6050的引脚定义 1.3MPU6050寄存器解析 二.MPU6050驱动开发 2.1 配置寄存器 2.2对MPU6050寄存器进行读写 2.2.1 写入寄存器 2.2.2读取寄存器 2.3 初始化MPU6050 2.3.1 设置工作模式 2.3.2 配置采样率 2.3.3 启…

MySQL | MySQL库、表的基本操作

MySQL库、表的基本操作01 一、库操作1.1 查看数据库1.2 创建数据库1.3 选择数据库1.4 查看创建数据库的SQL语句1.5 修改数据库1.6 删除数据库 二、表操作2.1 创建数据表2.2 查看表2.3 查看表结构2.4 查看创建数据库的SQL语句2.5 修改表2.6 删除表 ⚠️MySQL版本 8.0 一、库操作…

【Golang】go语言异常处理快速学习

Go 语言的异常处理与很多传统的编程语言不同,它没有 try/catch 这样的异常捕获机制,而是通过 错误类型(error)来进行错误处理。Go 语言鼓励显式地处理错误,保持代码的简单性和可维护性。在 Go 中,错误处理不…

Ollama部署本地大模型DeepSeek-R1-Distill-Llama-70B

文章目录 一、下模二、转模1. 下载转换工具2. 安装环境依赖3. llama.cpp1. 转换脚本依赖2. llama.cpp安装依赖包3. llama.cpp编译安装4. 格式转换 三、Ollama部署1. 安装启动Ollama2. 添加模型3. 测试运行 一、下模 #模型下载 from modelscope import snapshot_download model…

Rust 驱动的 Python 工具革命:Ruff 和 uv 与传统工具的对比分

Rust 驱动的 Python 工具革命:Ruff 和 uv 与传统工具的对比分析 概述: Python 生态系统长期以来依赖于一系列经典工具,如 Flake8、Black、pip 和 virtualenv,这些工具在代码检查、格式化和依赖管理方面发挥了重要作用。然而&…