当前位置: 首页 > news >正文

通透理清三级缓存--看Spring是如何解决循环依赖的

文章目录

    • 循环依赖是什么?
    • 三级缓存
      • 三级缓存是什么?
      • 执行流程
    • 疑点|困惑
      • 第二级缓存的存在必要性
      • 第三级缓存的存在必要性
        • 问题一:违反“按需创建”原则 → 性能浪费
        • 问题二:破坏“职责分离”与“扩展点机制”

三级缓存机制在 Spring 2.0 时代引入,主要解决两个问题:

  1. Setter 注入下的循环依赖(让应用能跑起来)
  2. AOP 代理对象的正确提前注入(让功能不被破坏)

循环依赖是什么?

循环依赖指的是两个类中的属性相互依赖对方:
例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环,如下图。
img
代码表现是这样的:

A.java
@Service
public class AService {private BService bService;@Autowiredpublic void setBService(BService bService) {this.bService = bService;}
}B.java
@Service
public class BService {private AService aService;@Autowiredpublic void setAService(AService aService) {this.aService = aService;}
}

应用启动后就会出现:

***************************
APPLICATION FAILED TO START
***************************Description:The dependencies of some of the beans in the application context form a cycle:┌─────┐
|  a defined in file [...A.class]
↑     ↓
|  b defined in file [...B.class]
└─────┘

三级缓存

Spring通过三级缓存提前暴露未完全初始化的对象引用,解决单例作用域 Bean 的 sette注入方式的循环依赖问题,接下来我将详细讲解三级缓存并分为有AOP和无AOP两种情况进行讨论,注意区分

三级缓存是什么?

三级缓存其实都是 Map类型的缓存

  1. 一级缓存(Singleton Objects)Map<String,Object>:这是一个Map类型的缓存,存储的是已经完全初始化好的bean,即完全准备好可以使用的bean实例。键是bean的名称,值是bean的实例。这个缓存在DefaultSingletonBeanRegistry类中的singletonObjects属性中。
  2. 二级缓存(Early Singleton Objects)Map<String,Object>:这同样是一个Map类型的缓存,存储的是早期的bean引用,即已经实例化但还未完全初始化的bean。这些bean已经被实例化,但是还没有进行属性注入等操作。这个缓存在DefaultSingletonBeanRegistry类中的earlySingletonObjects属性中。
  3. 三级缓存(Singleton Factories)Map<String,ObjectFactory>:这也是一个Map类型的缓存,存储的是ObjectFactory对象,这些对象**可以生成早期的bean引用。**当一个bean正在创建过程中,如果它被其他bean依赖,那么这个正在创建的bean就会通过这个ObjectFactory来创建一个早期引用,从而解决循环依赖的问题。这个缓存在DefaultSingletonBeanRegistry类中的singletonFactories属性中。
缓存级别名称存储内容目的
一级缓存singletonObjects完全初始化后的单例 Bean提供给用户使用的最终成品
二级缓存earlySingletonObjects提前暴露的“半成品”Bean(已实例化但未注入依赖)解决循环依赖时临时存放
三级缓存singletonFactoriesBean 的工厂对象(ObjectFactory用于生成早期引用(解决 AOP 代理问题)

执行流程

在这里插入图片描述

一个Bean的创建流程可以分为以下五步

以这段代码为例 AB循环依赖

@Component
public class A{@Autowiredprivate B b;
}@Component
public class B{@Autowiredprivate A a
}
  • 第一步:获取A对象 准备在缓存中获取A对象,分别在一级、二级、三级缓存中查找 如果找到就返回

在这里插入图片描述

  • 第二步:创建A对象 一开始缓存中并没有A对象,因此直接进行下一步–创建对象,这一步通过反射创建A对象并注册为ObjectFactory放入三级缓存中 ,这个工厂的使命是:当其他Bean需要引用BeanA时,它能动态返回当前这个半成品的BeanA(可能是原始对象,也可能是为应对AOP而提前生成的代理对象)此时BeanA的状态为已实例化但是还没注入属性也没初始化

在这里插入图片描述

  • 第三步:填充A属性 在这一步A在填充过程中发现需要注入B对象,进入B的实例化过程中,该过程与上述第一、第二步相同,在创建B对象时将B对象注册为ObjectFactory并放入三级缓存中,此时三级缓存中同时存在BeanABeanB的工厂

在这里插入图片描述

  • 第四步: 填充B属性 在填充B属性的过程中发现需要注入A对象,这时候重复第一步在缓存中查询A对象,这时候在三级缓存中可以找到了

    1. 在一级缓存(存放完整Bean)中未找到BeanA
    2. 在二级缓存(存放已暴露的早期引用)中同样未命中;
    3. 最终在三级缓存中定位到BeanA的工厂。

    Spring立即调用该工厂的getObject()方法。这个方法会执行关键决策:BeanA需要AOP代理,则动态生成代理对象(即使BeanA还未初始化);若无需代理,则直接返回原始对象。得到的这个早期引用(可能是代理)被放入二级缓存(earlySingletonObjects),同时从三级缓存清理工厂条目

在这里插入图片描述

  • 第五步:B执行初始化方法与缓存转移 执行@PostConstructafterPropertiesSet()等初始化方法将其转化为完整可用的Bean。随后,BeanB被提升至一级缓存(singletonObjects),二级和三级缓存中关于BeanB的临时条目均被清除。此时BeanB已准备就绪,可被其他对象使用
    在这里插入图片描述

  • 第六步:回溯A的执行 与上面第五步相同,A接着执行初始化方法与缓存转移,值得注意的是:如果A是一个需要代理的对象,会从二级缓存中获取此前存放的半成品代理对象(保证单例)并通过BeanPostProcessor(BPP后置处理器)进行AOP增强完善,随后将此代理对象放入一级缓存中同时清二级缓存

    在这里插入图片描述

以上就是三级缓存的完整执行流程


疑点|困惑

第二级缓存的存在必要性

在上述流程中似乎并没有发现第二级缓存发挥了怎样的作用,似乎都是使用的第三级缓存所返回的对象,那二级缓存的作用到底是什么呢?

思考一下下述场景 当A被多处循环依赖时

@Component
public class A{@Autowiredprivate B b;
}@Component
public class B{@Autowiredprivate A a;
}@Component
public class C{@Autowiredprivate A a;
}@Component
public class D{@Autowiredprivate A a;
}

这个场景中A被多处依赖,如果没有二级缓存,B、C、D所持有的都会是从三级缓存的ObjectFactory所生成的不同对象,这样就破坏了单例性。此外,当存在AOP时,Bean执行初始化方法时候会经过BeanPostProcessor处理,此时需要获取先前创建的半成品AOP代理对象进行完善以保证其他Bean依赖的是同一代理对象。所以,第二级缓存是为了保证Bean的单例性


第三级缓存的存在必要性

第三级缓存核心是为了正确处理需要 AOP 代理的 Bean——需要AOP代理时返回代理对象,不需要时返回原始对象

那么疑问来了:既然最终都要创建代理,为什么不在实例化后立即判断是否需要代理并创建,直接放入二级缓存,这样不就省去三级缓存?

我的理解主要有两点:按需创建&职责分离


这里先用一句话总结三级缓存存在的意义:

三级缓存(singletonFactories)存在的核心意义是:延迟代理对象的创建,直到真正需要时才生成,从而避免不必要的性能开销,并支持扩展点(如 SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference)在早期暴露阶段动态决定是否/如何代理。


为什么“实例化后就检查是否需要代理”看似合理,实则不可行?

问题一:违反“按需创建”原则 → 性能浪费

很多 Bean 虽然符合 AOP 切入点(如标注了 @Transactional),但 并没有被循环依赖

@Service
public class UserService {@Transactionalpublic void save() { ... } // 有事务,但没人循环依赖它
}

→ 如果在实例化后立即创建代理 → 即使没有循环依赖,也提前创建了代理对象!

代价:

  • 代理对象创建是有成本的(反射、CGLIB字节码生成、Advisor匹配等)
  • 多数 Bean 并不需要提前暴露 → 提前代理 = 浪费性能 + 内存

Spring 设计哲学:只有在发生循环依赖、需要提前暴露时,才创建早期代理对象。


问题二:破坏“职责分离”与“扩展点机制”

三级缓存中存放的是 ObjectFactory,它调用的是:

() -> getEarlyBeanReference(beanName, mbd, bean)

getEarlyBeanReference 会触发:

for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {bean = bp.getEarlyBeanReference(bean, beanName);
}

👉 这是一个扩展点

举例:

  • AbstractAutoProxyCreator:用于 AOP 代理
  • 未来你也可以自定义 SmartInstantiationAwareBeanPostProcessor 来在早期阶段包装 Bean(不只是代理,可能是装饰器、验证器、日志包装等)

如果直接在实例化后就判断是否代理 → 就绕过了这个扩展点机制,丧失了灵活性。


最终结论:

第三级缓存存在的意义,不是“偷懒”或“复杂化”,而是为了在解决循环依赖的同时,做到:按需创建、性能最优、扩展性强、语义安全。

如果在实例化后就立即创建代理,虽然能“简化”缓存层级,但会导致大量无用代理、破坏扩展机制、违背懒加载原则 —— 是典型的“为了简化结构而牺牲架构弹性与性能”的反模式。

Spring 选择三级缓存,是经过深思熟虑的平衡之选



文章转载自:

http://FZmfypyh.dgpxp.cn
http://B8HnlV8N.dgpxp.cn
http://yh2AbQDn.dgpxp.cn
http://1fXbstot.dgpxp.cn
http://IJJEOpku.dgpxp.cn
http://vINKzteF.dgpxp.cn
http://HevwAmrM.dgpxp.cn
http://HxkbfrMY.dgpxp.cn
http://bPfUTewI.dgpxp.cn
http://NN54HlzZ.dgpxp.cn
http://kTx3fkCm.dgpxp.cn
http://HT22mIGz.dgpxp.cn
http://42l3cCVm.dgpxp.cn
http://KszItNub.dgpxp.cn
http://uLiiYS6E.dgpxp.cn
http://HuwoAhFq.dgpxp.cn
http://Cs5Yxa31.dgpxp.cn
http://raTyTlJM.dgpxp.cn
http://LoRgf1Qr.dgpxp.cn
http://84wHmPub.dgpxp.cn
http://NJS1JpR0.dgpxp.cn
http://qsQrpYZ5.dgpxp.cn
http://ojDNSUiS.dgpxp.cn
http://2xPRYjny.dgpxp.cn
http://5vuIzmMV.dgpxp.cn
http://GR4vFZxF.dgpxp.cn
http://pIEgllzP.dgpxp.cn
http://tZNakftf.dgpxp.cn
http://X4ERUH7f.dgpxp.cn
http://1JL7BJiJ.dgpxp.cn
http://www.dtcms.com/a/386258.html

相关文章:

  • 【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
  • 查看 Docker 守护进程日志
  • 第11章 [特殊字符]️Hutool 常用工具类
  • 【MySQL|第十篇】总结篇——各种命令集合
  • npm : 无法加载文件 d:\nvm4w\nodejs\npm.ps1,
  • 贪心算法应用:活动选择问题详解
  • C++ 模板:以简御繁-5/5
  • AI大模型学习(6)Yolo V8神经网络的基础应用
  • 【完整源码+数据集+部署教程】残疾人和正常人识别图像分割系统: yolov8-seg-act
  • 深度学习:从概念到实践,开启智能时代新篇章
  • 构建AI智能体:三十五、决策树的核心机制(一):刨根问底鸢尾花分类中的参数推理计算
  • 美创科技入选 2025 年度省级场景型数字化服务商!
  • 《COD21》新赛季海量更新:《忍者神龟》联动上线!
  • RuoYi框架Excel静态模板下载例子Demo
  • 【系列文章】Linux系统中断的应用02-中断下文 tasklet
  • GPT-5-Codex 模型评测报告
  • MAZANOKE+cpolar让照片存储无上限
  • (笔记)Linux系统设置虚拟内存
  • Kotlin-基础语法练习三
  • windows上Redis Desktop Manager链接服务器docker内Redis方法
  • jMeter小记-数组数据X_id集合获取及循环控制器使用调用数组数据X_id
  • 迁移指南:从旧版 Electron 升级
  • Node.js中的 http 模块详解
  • 设置powershell每次打开自动启动anaconda中自设环境
  • keil5和arm编译器安装
  • 【初阶数据结构】顺序表
  • 外媒称Switch2手柄鼠标功能 将彻底改变玩游戏的方式
  • 【Spring Cloud】微服务
  • 设计模式(Java实现)----建造者模式
  • C++设计模式_创建型模式_建造者模式Builder