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

Spring使用三级缓存解决循环依赖的源码分析。

前言

本文将通过源码去解析,Spring为什么使用三级缓存机制去解决循环依赖,能够读懂本文 的基础是Spring生命周期和对三级缓存有基本的 认知才能理解更加深刻。还不了解Spring生命周期的源码和三级缓存的基本原理的话,可查看本文之前写过的两篇文章。

Spring管理Bean生命周期源码分析。
Spring为什么要用三级缓存解决循环依赖?

首先,我们需要回忆Spring处理循环依赖的三级缓存机制。三级缓存分别是singletonObjects、earlySingletonObjects和singletonFactories。我们要确保自己正确理解这三个缓存的作用和它们在解决循环依赖中的具体步骤。

接下来,要确定源码的关键入口。通常,循环依赖的处理发生在Bean的实例化和属性注入阶段,也就是在doCreateBean方法中。这里会提前暴露Bean的早期引用,并放入三级缓存。当另一个Bean在注入依赖时发现当前Bean正在创建中,就会从缓存中获取这个早期引用,从而解决循环依赖。

需要分析getBean、doGetBean、createBean和doCreateBean这几个关键方法。特别是getSingleton方法,这个方法会检查各级缓存,看看是否存在已经创建好的Bean或者早期引用。如果在创建过程中,Bean还没有完全初始化完成,就会通过ObjectFactory来提前暴露引用。

另外,要解释为什么需要三级缓存而不是两级。可能读者会疑惑,为什么不能只用两级?这里需要提到AOP代理的情况。如果Bean需要被代理,那么在早期暴露的时候,就需要通过singletonFactories中的ObjectFactory来生成代理对象,而不是直接放入二级缓存。这样可以确保代理对象的一致性和正确性。

读者可能还想知道循环依赖的限制,比如构造函数注入无法解决循环依赖的问题。这时候需要提到,Spring只能解决通过setter方法或字段注入的循环依赖,而构造函数注入因为在实例化时就需要依赖,三级缓存无法处理这种情况。

在源码分析时,要具体指出各个缓存的位置,比如DefaultSingletonBeanRegistry类中的三个Map。同时,要跟踪Bean的创建过程,展示如何从缓存中获取,如何添加早期引用,以及如何完成后续的属性注入和初始化。

可能还需要对比不同缓存的使用场景,比如singletonObjects存放完全初始化好的Bean,earlySingletonObjects存放早期引用,而singletonFactories存放的是生成早期引用的工厂。当需要获取Bean时,首先检查一级缓存,如果没有且正在创建中,则检查二级缓存,再没有就调用三级缓存中的工厂方法生成早期引用,并放入二级缓存。

最后,要总结三级缓存的流程,用流程图或步骤说明帮助读者理解整个过程。同时,指出常见的应用场景和限制,让读者不仅了解机制,还能在实际开发中避免相关问题。

需要确保解释清晰,避免术语混淆,同时结合具体的代码片段,比如getSingleton方法中的代码,展示如何逐级查找缓存。此外,要提到Spring在解决循环依赖时如何保证线程安全,比如通过synchronized同步块。

读者可能的疑问点还包括:为什么需要提前暴露引用?三级缓存的每个步骤在何时触发?如何处理代理对象的循环依赖?这些都需要在回答中详细说明,确保读者能够全面理解。

Spring 通过三级缓存解决循环依赖(Circular Dependency)的核心思想是提前暴露未完成初始化的 Bean 的引用。以下章节是源码分析(基于 Spring 5.x 版本)。

Spring 通过三级缓存解决循环依赖(Circular Dependency)的核心思想是提前暴露未完成初始化的 Bean 的引用。以下是源码分析(基于 Spring 5.x 版本):


一、三级缓存定义

DefaultSingletonBeanRegistry 类中定义三级缓存:

public class DefaultSingletonBeanRegistry ... {// 一级缓存:存放完全初始化好的单例 Bean(成品)private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存:存放提前暴露的早期 Bean 对象(半成品,未完成属性注入和初始化)private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);// 三级缓存:存放 Bean 的 ObjectFactory(用于生成早期引用)private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);// 记录正在创建中的 Bean 名称(标记当前 Bean 正在创建)private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}

二、解决循环依赖的源码流程

假设 Bean A 依赖 Bean B,Bean B 依赖 Bean A,流程如下:

1. 创建 Bean A
  • 入口AbstractBeanFactory#getBean() -> doGetBean() -> createBean() -> doCreateBean()
  • 步骤
    1. 标记 Bean A 正在创建
      beforeSingletonCreation(beanName); // 将 "A" 加入 singletonsCurrentlyInCreation
      
    2. 实例化 Bean A(调用构造方法)
    3. 提前暴露 Bean A 的早期引用
      将 Bean A 的 ObjectFactory 放入三级缓存(singletonFactories):
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
      
      • getEarlyBeanReference() 会处理 AOP 代理(若有)。
2. 填充 Bean A 的属性(依赖 B)
  • 入口AbstractAutowireCapableBeanFactory#populateBean()
  • 触发获取 Bean B
    // 解析依赖 B,触发 getBean("B")
    Object value = getBean(propertyName);
    
3. 创建 Bean B
  • 入口getBean("B") -> doGetBean() -> createBean() -> doCreateBean()
  • 步骤
    1. 标记 Bean B 正在创建
    2. 实例化 Bean B
    3. 提前暴露 Bean B 的早期引用(放入三级缓存)。
4. 填充 Bean B 的属性(依赖 A)
  • 再次触发获取 Bean A
    Object value = getBean("A");
    
  • 获取 Bean A
    • 检查一级缓存(singletonObjects)→ 未找到。
    • 检查二级缓存(earlySingletonObjects)→ 未找到。
    • 检查三级缓存(singletonFactories)→ 找到 Bean A 的 ObjectFactory
    • 通过 ObjectFactory 获取 Bean A 的早期引用
      Object singletonObject = singletonFactory.getObject();
      
    • 将 Bean A 的早期引用从三级缓存移到二级缓存
      this.earlySingletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
      
    • 返回 Bean A 的早期引用(此时 Bean A 未完成属性注入和初始化)。
5. Bean B 完成初始化
  • 将 Bean B 放入一级缓存(singletonObjects)。
6. Bean A 完成初始化
  • 将 Bean A 从二级缓存(earlySingletonObjects)移到一级缓存(singletonObjects)。

三、关键源码方法解析

1. getSingleton():获取单例 Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 检查一级缓存Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 2. 检查二级缓存singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {// 3. 检查三级缓存,生成早期引用ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();// 将引用从三级缓存移到二级缓存this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}
2. addSingletonFactory():暴露早期引用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {// 将 ObjectFactory 放入三级缓存this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);}}
}

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

  1. 一级缓存(singletonObjects:存放完全初始化的 Bean,直接对外提供使用。
  2. 二级缓存(earlySingletonObjects:避免重复执行 ObjectFactory.getObject()(如多次依赖注入)。
  3. 三级缓存(singletonFactories
    • 延迟生成早期引用(直到真正需要时)。
    • 支持 AOP 代理:通过 ObjectFactory 动态生成代理对象(见 SmartInstantiationAwareBeanPostProcessor)。

五、循环依赖的限制

  1. 构造函数注入无法解决循环依赖
    实例化阶段(构造方法调用)无法提前暴露引用。
  2. 原型(Prototype)作用域的 Bean 不支持循环依赖
    Spring 不缓存原型 Bean 的早期引用。

六、流程图解

Bean A 创建流程:
1. 实例化 A → 暴露 ObjectFactory(三级缓存)↓
2. 填充属性 B → 触发创建 B↓           ↑↓       Bean B 创建流程:↓       1. 实例化 B → 暴露 ObjectFactory(三级缓存)↓       2. 填充属性 A → 从三级缓存获取 A 的早期引用↓       3. B 初始化完成 → 移入一级缓存↓
3. A 完成初始化 → 移入一级缓存

通过三级缓存,Spring 在属性注入阶段通过提前暴露未完成初始化的 Bean 引用,解决了循环依赖问题。


http://www.mrgr.cn/news/91985.html

相关文章:

  • Redis基础学习
  • STM32-心知天气项目
  • nodejs:express + js-mdict 作为后端,vue 3 + vite 作为前端,在线查询英汉词典
  • 大模型WebUI:Gradio全解12——LangChain原理及其agent构建Gradio(1)
  • JDBC学习
  • 【c语言】函数_作业详解
  • 嵌入式八股文(四)计算机网络篇
  • LTO优化详解
  • FFMPEG编码容错处理解决办法之途径----升级库文件
  • 类与对象(4)
  • 探索YOLO技术:目标检测的高效解决方案
  • windows的CMD命令提示符
  • 政安晨【零基础玩转各类开源AI项目】DeepSeek 多模态大模型Janus-Pro-7B,本地部署!支持图像识别和图像生成
  • ubuntu新系统使用指南
  • 1.vue使用vite构建初始化项目
  • java开发——为什么要使用动态代理?
  • Codes 开源免费研发项目管理平台 2025年第一个大版本3.0.0 版本发布及创新的轻IPD实现
  • 目标检测数据集-水果腐烂新鲜度检测数据集(适用YOLO全系列)
  • OpenHarmony构建系统-GN与子系统、部件、模块理论与实践
  • (新)01前缀和来临!优点多多!