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

[设计模式与源码]1_Spring三级缓存中的单例模式

欢迎来到啾啾的博客🐱,一个致力于构建完善的Java程序员知识体系的博客📚,记录学习的点滴,分享工作的思考、实用的技巧,偶尔分享一些杂谈💬。
欢迎评论交流,感谢您的阅读😄。

本篇总结了Spring单例Bean循环依赖的解决机制——“三级缓存”,其本质是创建过程依赖的解耦,提前暴露对象引用,利用多级缓存机制存储引用与实例对象。

需要先理解SpringBean的生命周期,不理解也没关系,看一下doCreateBean方法也就记住了。

⬅️前文

设计模式概览

目录

单例模式-Spring DefaultSingletonBeanRegistry

三级缓存解决单例Bean循环依赖

三级缓存机制

循环依赖具体解决步骤

循环依赖限制

单例Bean限制

构造器注入限制

单例模式-Spring DefaultSingletonBeanRegistry

单例模式定义为“确保一个类只有一个实例,并提供一个全局访问点来访问这个实例”。

Spring默认的Bean作用域就是单例的,每个Bean在Spring容器中只有一个实例。

Spring框架DefaultSingletonBeanRegistry类,使用CouncurrentHashMap存储单例对象,并提供getSingleton方法获取对象。

DefaultSingletonBeanRegistry中的单例模式代码提取如下:

public class DefaultSingletonBeanRegistry {
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    public Object getSingleton(String beanName) {
        return singletonObjects.get(beanName);
    }
}

值得注意的是,一级缓存初始化时指定了256的初始容量,在Java基础-集合篇中有提到这是一种常见的提升性能的写法。

同时,这段代码也可以用于解释Spring是如何通过三级缓存机制来解决单例Bean的依赖循环问题。

三级缓存解决单例Bean循环依赖

以 Bean A → 依赖 Bean B → 依赖 Bean A 的循环依赖场景为例。

总所周知,Bean的生命周期为实例化、属性注入、初始化、使用、销毁。

在BeanA实例化后进行属性注入操作创建属性BeanB时,发现BeanB属性注入操作需要创建BeanA,此时便形成了循环依赖。

Spring通过三级缓存机制来解决循环依赖。

三级缓存机制

  • 一级缓存(singletonObjects):存储完全初始化的单例Bean。

  • 二级缓存(earlySingletonObjects):存储提前暴露的Bean(未完成属性注入)。

  • 三级缓存(singletonFactories):存储Bean的工厂对象,用于生成提前暴露的Bean。

核心机制是二级缓存的对象早期引用Early Bean Reference,相当于在对象完成初始化前体现暴露对象引用。

辅助理解:类似面向接口编程思想,体现暴露对象引用相当于提供接口,创建Bean不管具体实现,实现创建过程的解耦,进而解决循环依赖问题。

循环依赖具体解决步骤

1.Bean A实例化后
        将ObjectFactory存入三级缓存(此时A尚未完成属性注入)
        触发populateBean()时发现需要注入Bean B
2.创建Bean B
        实例化Bean B后同样存入三级缓存
        触发populateBean()时发现需要注入Bean A
3.获取Bean A的早期引用
        通过getSingleton("A")从三级缓存获取ObjectFactory
        通过工程对象调用getEarlyBeanReference()获取未完成初始化的BeanA对象,即BeanA的早期引用(Early Bean Reference)
4.完成Bean B的初始化
        Bean B获得A的代理对象后完成初始化
        Bean B被存入一级缓存
5.继续完成Bean A的初始化
        将Bean B注入到Bean A
        Bean A完成初始化后存入一级缓存
        清除二级缓存中的A对象

结合AbstractAutowireCapableBeanFactory源码分析,创建Bean时,实例化后将工程对象存入三级缓存,用于后续创建对象早期引用。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 1. 实例化Bean A(此时对象未初始化)
    Object bean = instanceWrapper.getWrappedInstance();
    
    // 2. 将Bean A的工厂对象存入三级缓存(关键步骤!)
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    
    // 3. 属性注入(触发Bean B的创建)
    populateBean(beanName, mbd, instanceWrapper);
    
    // 4. 初始化(调用InitializingBean等)
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    return exposedObject;
}

核心方法⬇️


protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
       if (!this.singletonObjects.containsKey(beanName)) {
           // 存入三级缓存
          this.singletonFactories.put(beanName, singletonFactory);
          // 确保二级缓存中没有残留
          this.earlySingletonObjects.remove(beanName);
          this.registeredSingletons.add(beanName);
       }
    }
}

获取Bean的源码提取如下⬇️:

public class DefaultSingletonBeanRegistry {
    // 一级缓存:存放完全初始化的Bean(成品对象)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    
    // 二级缓存:存放早期暴露的Bean(半成品对象,未完成属性注入)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();
    
    // 三级缓存:存放Bean工厂对象(用于生成半成品对象)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

    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) {
                        // 生成早期引用(可能被AOP代理)
                        singletonObject = singletonFactory.getObject();
                        // 升级到二级缓存
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
}

补充源码图如下:

循环依赖限制

三级缓存机制仅能解决单例Bean且非构造器注入的循环依赖。

单例Bean限制

原型(Prototype)作用域的Bean每次请求都会创建新实例,即提前暴露引用也没用,引用会发生变化,无法通过缓存保存中间状态,因此无法解决循环依赖。

创建bean的源码AbstractAutowireCapableBeanFactory.doCreateBean中也做了bean是否是singleton的判断

构造器注入限制

构造器注入无法解决循环依赖。构造器注入要求在实例化Bean时立即注入依赖,而Spring需要实例化完成后才能提前暴露对象引用。构造器循环依赖时,Bean尚未实例化,无法提前暴露引用,导致死锁。

@Service
public class A {
    private final B b;

    @Autowired // 构造器注入B,创建B需要注入A
    public A(B b) {
        this.b = b;
    }
}

@Service
public class B {
    private final A a;

    @Autowired
    public B(A a) { // 构造器注入A,创建A需要注入B
        this.a = a;
    }
}

相关文章:

  • 设计模式(行为型)-状态模式
  • Leetcode 刷题笔记1 单调栈part01
  • UART转AHB模块ModelSim仿真
  • C语言每日一练——day_10
  • 冒泡排序:古老算法中的智慧启示
  • c++学习系列----003.写文件
  • MySQL——数据类型
  • Postman 新手入门指南:从零开始掌握 API 测试
  • 嵌入式Linux | 什么是 BootLoader、Linux 内核(kernel)、和文件系统?
  • 基于javaweb的SpringBoot智能相册管理系统图片相册系统设计与实现(源码+文档+部署讲解)
  • 音视频处理的“瑞士军刀”与“积木”:FFmpeg 与 GStreamer 的深度揭秘
  • 【系统架构设计师】操作系统 - 文件管理 ③ ( 树形目录结构 | 文件属性 | 绝对路径 与 相对路径 )
  • C++类:特殊的数据成员
  • Linux环境使用jmeter做性能测试
  • 全球化2.0 | ZStack云计算系统工程师(ZCCE)国际认证培训成功举办
  • win10 c++ VsCode 配置PCL open3d并显示
  • 猎豹移动(Cheetah Mobile)
  • 【Unity】TextMesh Pro显示中文部分字体异常
  • 基于FPGA的3U机箱模拟量高速采样板ADI板卡,应用于轨道交通/电力储能等
  • 游戏引擎学习第157天
  • 马上评丨别让“免费领养”套路坑消费者又坑宠物
  • 文天祥与“不直人间一唾轻”的元将唆都
  • 丁俊晖连续7年止步世锦赛16强,中国军团到了接棒的时候
  • 自称“最美”通缉犯出狱当主播?央广网:三观怎能跟着“五官”跑
  • 赛力斯拟赴港上市:去年扭亏为盈净利59亿元,三年内实现百万销量目标
  • 科学时代重读“老子”的意义——对谈《老子智慧八十一讲》