[设计模式与源码]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;
    }
}
相关推荐
Warren981 分钟前
Lua 脚本在 Redis 中的应用
java·前端·网络·vue.js·redis·junit·lua
艾伦~耶格尔4 小时前
【数据结构进阶】
java·开发语言·数据结构·学习·面试
爪洼传承人4 小时前
18- 网络编程
java·网络编程
smileNicky4 小时前
SpringBoot系列之从繁琐配置到一键启动之旅
java·spring boot·后端
祈祷苍天赐我java之术5 小时前
Java 迭代器(Iterator)详解
java·开发语言
David爱编程5 小时前
为什么必须学并发编程?一文带你看懂从单线程到多线程的演进史
java·后端
我命由我123455 小时前
软件开发 - 避免过多的 if-else 语句(使用策略模式、使用映射表、使用枚举、使用函数式编程)
java·开发语言·javascript·设计模式·java-ee·策略模式·js
long3165 小时前
java 策略模式 demo
java·开发语言·后端·spring·设计模式
摇滚侠5 小时前
HTML <iframe> 标签 如何把html写入iframe标签
java
云间月13146 小时前
飞算JavaAI:从智能调度到出行服务的全链路技术升级
java·redis·飞算javaai炫技赛