spring的三级缓存及二三级缓存解决的问题 - 指南

news/2025/12/3 22:29:03/文章来源:https://www.cnblogs.com/yangykaifa/p/19304203

一、三级缓存解决单例循环依赖的完整流程

Spring通过三级缓存(singletonObjectsearlySingletonObjectssingletonFactories)配合单例Bean的循环依赖(如A依赖B,B依赖A),核心是提前暴露Bean的早期引用,同时保证Bean的单例性和完整性。

三级缓存定义:
  1. 一级缓存(singletonObjects):存储完全初始化达成的单例Bean,是最终对外提供的实例。
  2. 二级缓存(earlySingletonObjects):存储提前暴露的早期引用(可能是原始Bean或AOP代理),用于解决循环依赖时的临时缓存。
  3. 三级缓存(singletonFactories):存储ObjectFactory工厂对象,用于延迟创建早期引用(原始Bean或代理),避免提前固定Bean形态。
解决循环依赖的完整步骤(以A依赖B,B依赖A为例):
  1. 创建A的实例

    • 执行A的构造器(实例化),生成原始对象A(未注入依赖,未初始化)。
    • 创建ObjectFactory工厂对象,存入三级缓存singletonFactories。工厂的getEarlyBeanReference方法逻辑:若A需要AOP代理,则创建代理;否则返回原始对象A。
  2. A需要注入B

    • 检查一级/二级缓存,未找到B(B尚未创建),触发B的创建流程。
  3. 创建B的实例

    • 执行B的构造器(实例化),生成原始对象B(未注入依赖,未初始化)。
    • 创建ObjectFactory工厂对象,存入三级缓存singletonFactories
  4. B需要注入A

    • 检查一级缓存(无,A未完成)→ 检查二级缓存(无)→ 从三级缓存获取A的工厂。
    • 调用A的工厂getEarlyBeanReference方法,生成A的早期引用(若A需要AOP,则是代理对象P;否则是原始A)。
    • 将A的早期引用存入二级缓存earlySingletonObjects,并移除三级缓存中的A工厂(避免重复创建)。
    • B注入A的早期引用(代理P或原始A),结束依赖注入。
  5. B达成初始化

    • B执行初始化方法(如@PostConstruct),成为完整Bean,存入一级缓存singletonObjects
  6. A继续初始化

    • A注入已完成的B(从一级缓存获取),执行初始化办法,成为完整Bean。
    • 检查二级缓存:若存在A的早期引用(如代理P),则将其移至一级缓存(替代原始A,保证对外暴露的是代理);若不存在,则将原始A存入一级缓存。
  7. 最终结果

    • A和B均为完整Bean,且相互引用的是对方的有效实例(AOP代理或原始对象),循环依赖消除。

二、二级缓存(earlySingletonObjects)解决的核心挑战

二级缓存是“早期引用的临时仓库”,克服的是“避免同一Bean的早期引用被重复创建”的问题,直接保障单例唯一性。

具体场景:

若多个Bean依赖同一个Bean(如B、C都依赖A),且A需要AOP代理:

  • 无二级缓存时:B依赖A时调用A的工厂创建代理P1,C依赖A时再次调用工厂创建代理P2,导致A的代理不唯一(破坏单例)。
  • 有二级缓存时:B依赖A时创建代理P并存入二级缓存,C依赖A时直接从二级缓存获取P,确保代理唯一。
核心作用:

三、三级缓存(singletonFactories)解决的核心问题

三级缓存是“延迟创建早期引用的工厂容器”,解决的是“循环依赖时如何安全暴露Bean引用” 和“AOP代理的按需创建”问题。

核心作用1:解决循环依赖的“引用暴露时机”困难

循环依赖的本质矛盾是“Bean未初始化完成却需要被引用”。三级缓存的工厂通过以下方式应对:

核心作用2:保证AOP代理的“按需创建”与“状态完整”
  • 无循环依赖时:工厂不会被调用,AOP代理在Bean初始化后创建(遵循“初始化后增强”原则)。
  • 有循环依赖时:工厂被触发,提前创建代理,但代理内部的目标Bean会继续完成初始化(最终代理关联完整Bean)。
  • 若直接暴露原始Bean或代理(无工厂),会导致AOP代理失效或关联不完整状态(如实例化后直接创建代理,目标Bean未初始化)。

四、构造器注入的循环依赖与@Lazy解决方式

问题:构造器注入为何容易引发循环依赖?

“实例化阶段”的依赖注入(通过构造器参数注入),而Bean的实例化必须在依赖注入之前。若A的构造器依赖B,B的构造器依赖A,则:就是构造器注入

  • 创建A时,需要先实例化B(因为A的构造器需B);
  • 创建B时,需要先实例化A(由于B的构造器需要A);
  • 陷入“先有鸡还是先有蛋”的死循环,三级缓存无法应对(三级缓存作用于“实例化后”,而构造器注入在“实例化中”)。
@Lazy注解的应对原理

@Lazy通过延迟初始化依赖对象,将“构造器注入的即时依赖”转为“代理对象的延迟引用”,打破循环。

示例(A和B相互构造器依赖):
@Component
public class A {
private B b;
// A的构造器依赖B,添加@Lazy
public A(@Lazy B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
// B的构造器依赖A,添加@Lazy
public B(@Lazy A a) {
this.a = a;
}
}
解决流程:
  1. 创建A时,Spring发现B的注入带有@Lazy,不直接实例化B,而是创建一个B的代理对象(空壳代理),注入A的构造器。
  2. A成功实例化(依赖的是B的代理,而非真实B),后续流程正常执行(存入三级缓存、初始化等)。
  3. 创建B时,同理,Spring创建A的代理对象注入B的构造器,B成功实例化。
  4. 当A或B首次调用对方的方法时,代理对象才会触发真实对象的初始化(此时对方已完成创建),循环依赖被打破。
@Lazy的核心逻辑:

总结

  • 三级缓存:通过工厂延迟暴露早期引用,处理循环依赖,同时保证AOP代理按需创建和状态完整。
  • 二级缓存:缓存早期引用,避免重复创建,保障单例唯一性。
  • 构造器循环依赖:因发生在实例化阶段,三级缓存无效,需通过@Lazy创建延迟代理,推迟依赖的真实初始化时机。

三者共同构成了Spring解决单例循环依赖的完整体系,体现了“原则性(如单例、AOP增强时机)”与“灵活性(如提前暴露、延迟代理)”的平衡。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/986102.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

个人学习匿名内部类转lambda表达式转方法引用运算符的一个记录 - 亚麻青

首先我先来讲一下匿名内部类。 匿名内部类是内部类的一种,他的特点是无名字,直接进行实例化操作,最常见的就是接口和/抽象类的实例化。 使用条件:1、必须继承一个类或者实现一个接口。2、只能实现单继承。3、必须创…

函数指针与函数对象

函数指针与函数对象一、函数指针是什么? 函数指针的定义:在 C++ 中就是指向函数的指针变量,类型为 返回值类型(*)(参数类型列表),它保存的是函数的地址。通过函数指针,你可以动态调用指针指向的函数,实现更灵活的…

敏捷冲刺日志 - Day 5

敏捷冲刺日志 - Day 5 站立会议 站立时会议改为线上进行。昨天已完成的工作:saveVideoToGallery 功能已实现并测试通过。 初步定位了“替换”闪退问题的原因是 SecurityException。今天计划完成的工作:核心任务:尝试修…

12月3日日记

1.今天学习java web以及数据结合的复习 2.明天体育课打比赛 3.如何配置 StringRedisTemplate 和 RedisTemplate 的序列化(避免乱码 + 支持对象存储)?

第五篇Scrum冲刺博客

每日Scrum报告 日期: 2025-11-30 会议时间: 09:00 1. 当日站立式会议记录 会议照片成员同步内容 成员:齐思贤昨天已完成的工作:开发个人资料查询接口(GET /api/v1/users/me),返回用户详情(含review_count/coll…

敏捷冲刺日志 - Day 6

敏捷冲刺日志 - Day 6 站立会议 站立时会议改为线上进行。昨天已完成的工作:编写了兼容 Android 10 和 Android 11+ 的文件删除逻辑。 测试发现 Android 10 的 RecoverableSecurityException 方案不稳定。今天计划完成…

深入解析:Spring Kafka消费者被踢出组?CommitFailedException异常全面解析与解决方案

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

OWASP Java HTML 清理库曝出 XSS 漏洞:noscript 与 style 标签组合成隐患

OWASP Java HTML Sanitizer 库在某些特定策略配置下(允许 noscript 和 style 标签并允许 style 标签内含文本),存在跨站脚本漏洞。攻击者可构造特殊载荷绕过清理,导致 XSS 攻击。CVE-2025-66021:OWASP Java HTML …

敏捷冲刺日志 - Day 4

敏捷冲刺日志 - Day 4 站立会议 站立时会议改为线上进行。昨天已完成的工作:实现了视频的多选和队列压缩功能。 UI 可以正确显示批量压缩的进度。今天计划完成的工作:新功能:实现“保存到相册”功能,确保用户压缩的视…

计算机视觉黄金时代的回顾与展望

文章记录了资深科学家梅迪奥尼分享其40多年计算机视觉研究历程,重点阐述了从理解客户问题出发、逆向发明技术的方法论,并介绍了其在无感支付等场景下的实际应用,认为当前是计算机视觉解决现实商业问题的黄金时代。1…

homebrew运行机制

🍺 Homebrew 的结构 = 一个“酿酒厂” 以下是核心概念的真实意义 + 隐喻意义:1. Formula(配方) 真实意义: Homebrew 安装“源码软件”的脚本,描述软件从哪里下载、如何编译、有哪些依赖等。 隐喻: 👉 配方:…

解码构造与析构

构造与析构基础概念 核心定义构造函数:对象被创建时自动调用的特殊成员函数,唯一作用是初始化对象的成员属性,确保对象创建后处于合法可用状态。 析构函数:对象被销毁前自动调用的特殊成员函数,用于释放对象占用的…

敏捷冲刺日志 - Day 2

敏捷冲刺日志 - Day 2 站立会议 站立时会议改为线上进行。昨天已完成的工作:熟悉了项目初始版本(v1.0)的代码结构和功能。 分析了用户提出的三个核心需求:修复权限异常、界面汉化、增加批量处理。今天计划完成的工作…

10.结构型 - 代理模式 (Proxy Pattern)

代理模式 (Proxy Pattern) 在软件开发中,由于一些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为"代理"的第三者来实现间接访问.该方案对应的设计模式被称为代理模式. 代理模式(Proxy Design …

敏捷冲刺日志 - Day 1

敏捷冲刺日志 - Day 1 各个成员在 Alpha 阶段认领的任务 本次七天冲刺即为项目的 Alpha 阶段,目标是交付一个具备核心功能、可运行、可演示的最小可行产品(MVP)。团队成员在本阶段的任务分配如下:刘瑞康 (开发):负…

2025年中国集成灶十大品牌综合实力榜:选购指南与权威解析

body { font-family: "Microsoft YaHei", sans-serif; line-height: 1.8; color: rgba(51, 51, 51, 1); max-width: 1000px; margin: 0 auto; padding: 20px; background-color: rgba(249, 249, 249, 1) } h…

朝花夕拾OI回忆录

朝花夕拾 OI 回忆录 序言 或许是因为喜欢追忆吧,也或许是临近AFO,内心有一些触动,又或者是为了给后续的OIer一些前者的失败经验吧……总之,2025年12月3日,我十六岁生日这天,我决定写这篇 OI 回忆录,以记录我对O…

细胞因子:细胞信使的分子世界与功能解析

在复杂的多细胞生物体内,细胞间的信息交流是维持生命活动的基础。其中,细胞因子 作为一类关键的信使分子,在免疫调节、细胞生长、分化、炎症反应和组织修复等过程中扮演着不可或缺的角色。本文将深入探讨细胞因子的…

NOIp 的 p 是 painting 的 p!

哇还有连续剧。 作者在 CSP 后推完魔宴正在推 WA2。 Day -6 发现惊天理论:Day -3 最后的 ZR 有点娱乐赛,T1 是哈希表广告题,赛时裸 umap 拿了 90pts,赛后拿 umap 和 gp 卡了一万年卡不过,严肃学习 Dzb 牌哈希表,…

AWS云计算入门指南:从零到一,详解核心服务与免费套餐 - 教程

AWS云计算入门指南:从零到一,详解核心服务与免费套餐 - 教程2025-12-03 22:00 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !impor…