[设计模式与源码]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;
    }
}
相关推荐
小诸葛的博客11 分钟前
开发一个go模块并在其他项目中引入
开发语言·后端·golang
Emma歌小白11 分钟前
初步使用UML设计代码结构
后端
剽悍一小兔33 分钟前
Java8默认方法の终极奥义
后端
Emma歌小白36 分钟前
UML(Unified Modeling Language,统一建模语言)应用方向
后端
AAA_boy42 分钟前
Spring Boot 中使用 Function 和异步线程池处理列表拆分任务并汇总结果
java
Kale又菜又爱玩43 分钟前
Apache Shiro 使用教程
java·apache·springboot
雷渊44 分钟前
mybatis底层为什么设计二层缓存?
java·后端·面试
不修×蝙蝠1 小时前
SpringBoot 第一课(Ⅲ) 配置类注解
java·spring boot·spring·bean·propertysource·profile·importresource
祝瑾萱1 小时前
Go语言的负载均衡
开发语言·后端·golang
java技术小馆1 小时前
Kafka的流量控制机制
java·分布式·kafka