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

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.kler.cn/a/558214.html

相关文章:

  • AI IDE - Trae -学习与实践
  • git使用-克隆远程项目、分支管理
  • 浅谈小程序内嵌h5分享
  • 【Mysql:数据库的基础操作】
  • Redis 缓存穿透、击穿、雪崩:问题与解决方案
  • 大语言模型:如何用AI快速定制技能,挖掘海量数据的“宝藏”?
  • MySQL存储引擎:选择与应用
  • 计算机毕业设计SpringBoot+Vue.js学生读书笔记共享(源码+LW文档+PPT+讲解+开题报告)
  • 食物照片识别卡路里(深度学习)
  • Java 中的 List 和 Map:全面解析与实际应用
  • 在 .NET 8/9 中使用 AppUser 进行 JWT 令牌身份验证
  • 加班限制了进步
  • 虚拟机从零实现机器人控制
  • Docker构建时,设定默认进入的工作目录的方法
  • QT之改变鼠标样式
  • 数据库管理-第295期 IT架构与爆炸半径(20250221)
  • 使用MyBatis映射器实现对数据库的增删改查操作
  • rpc到自己java实现rpc调用再到rpc框架设计
  • go语言 创建kratos框架工程
  • (安全防御)DNS透明代理