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

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

欢迎评论交流,感谢您的阅读😄。

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

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

⬅️前文

设计模式概览

目录

[单例模式-Spring DefaultSingletonBeanRegistry](#单例模式-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;
    }
}
相关推荐
Tsuki_tl3 分钟前
【总结】Redis五大数据类型应用场景及基本命令
数据库·redis·后端·缓存·缓存中间件·redis命令·redis 五大数据类型
后台模板学习5 分钟前
Go Web开发中任务调度框架的源码解析与高可用实现方案
开发语言·后端·golang
系统集成-鹰翔9 分钟前
ERP对接升级提示数据库迁移失败:SQLite逐版本事务到底回滚到哪一步
javascript·数据库·经验分享·后端
西瓜拿铁好喝9 分钟前
开源和闭源 AI 办公工具,分别适合谁
java·前端·数据库
IT_陈寒9 分钟前
深入理解SpringBoot:核心原理与最佳实践
前端·人工智能·后端
学长毕设11 分钟前
基于SpringBoot的校园礼服租赁系统的设计与实现
java·spring boot·后端
明天也要努力学习17 分钟前
深入理解 JVM 垃圾回收(GC)
java·jvm
golang学习记24 分钟前
IDEA 断点调试技巧:拖拽
java·ide·intellij-idea
Sayai26 分钟前
Flink 日志解析任务性能优化实战:正则/Grok 预编译、Guava 缓存权重陷阱与 ES 写入隔离
java·大数据·性能优化·flink·实时计算
计算机毕设定制辅导-无忧学长28 分钟前
《基于物品协同过滤的闲置书换购微信小程序设计开发》
java·vue.js·spring boot·微信小程序·uniapp·闲置书换购微信小程序·物品协同过滤算法