关于spring三级缓存的解读

 更新时间:2025年02月12日 09:28:45   作者:ambity_lyf  
Spring三级缓存解决循环依赖、AOP和多线程问题,包括singletonObjects、earlySingletonObjects和singletonFactories三层缓存,通过不同方法获取bean并解决这些问题

spring三级缓存的解读

spring 中为了解决 B的重复利用,A 依赖B 的循环依赖,aop 问题,多线程可能拿到不完整的bean 的问题引入了3层缓存,分别是

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  • singletonObjects 用于存放单例bean 实例
  • earlySingletonObjects 用于存放早期的bean 实例
  • singletonFactories 用于存放简单工厂实例

从3级缓存中 获取bean 的方法有如下两个

1.从缓存中获取,主要用来解决循环依赖问题

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // 这里不用考虑指令重排,因为 bean 初始化完成后才会放入map
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      // 加锁,与另一个getSingleton 方法 互斥访问,保证并发情况下获取到不完整的bean
      synchronized (this.singletonObjects) {
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            // 获取工厂bean ,因为可能是aop 生成的代理,这里二级和三级缓存保证获取到的bean 是最后的完整bean
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               // 放入 二级缓存中,从3级缓存中移除
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

2.创建bean 的真正逻辑

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(beanName, "Bean name must not be null");
   synchronized (this.singletonObjects) {
      // 再次从一级缓存中获取,因为多线程加载同一个bean 时,
      //相当于 双重检查
      Object singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
         if (this.singletonsCurrentlyInDestruction) {
            throw new BeanCreationNotAllowedException(beanName,
                  "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                  "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
         }
         if (logger.isDebugEnabled()) {
            logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
         }
         // 标记bean 正在创建
         beforeSingletonCreation(beanName);
         boolean newSingleton = false;
         boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
         if (recordSuppressedExceptions) {
            this.suppressedExceptions = new LinkedHashSet<>();
         }
         try {
            singletonObject = singletonFactory.getObject();
            newSingleton = true;
         }
         catch (IllegalStateException ex) {
            // Has the singleton object implicitly appeared in the meantime ->
            // if yes, proceed with it since the exception indicates that state.
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               throw ex;
            }
         }
         catch (BeanCreationException ex) {
            if (recordSuppressedExceptions) {
               for (Exception suppressedException : this.suppressedExceptions) {
                  ex.addRelatedCause(suppressedException);
               }
            }
            throw ex;
         }
         finally {
            if (recordSuppressedExceptions) {
               this.suppressedExceptions = null;
            }
            afterSingletonCreation(beanName);
         }
         if (newSingleton) {
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}

我们先假设不存在aop 与多线程竞争的情况,此时我们只需要一级缓存就可以解决循环依赖的问题

  • 1.构造bean A ->
  • 2.把A 放入 map1 ->
  • 3. 填充A的属性 ->
  • 4.创建bean B ->
  • 5.填充B->
  • 6.从缓存中拿到A->
  • 7.返回B ->
  • 8返回A

即不存在aop且不考虑多线程的情况下,只要一个map1 我们就能解决循环依赖的问题

3.现在我们考虑aop

假设A 有属性B ,B 有属性A ,且我们对A 做了aop,在创建B 的时候我们希望拿到的是A 的aop 代理对象,此时假设填充B 的时候获取A ,

假设如果我们用一层缓存解决?

可以对是否aop 代理过存储一个标志位,此时必然在后面获取bean 的时候都要多一层判断,所以此时需要二层缓存来解决

一层存放aop 代理过的对象或不需要aop 的对象,一层存放非aop 对象,即创建工厂。

4.我们再考虑并发的情况,即多线程获取一个单例

这里如果熟悉单例模式的创建过程,就不难理解第二个方法中的synchronized 与加锁后再次尝试从一级缓存中获取,把第一个方法可以看作第一次非空判断,那么就是双重锁检查保证单例。但是只做了双重锁检查保证单例可以支持多线程操作吗?

答案是否定的,假设我们为了解决aop 使用了二层缓存,对于A 来说 并没有完全完成赋值和初始化的操作,但是由于A 依赖B ,B依赖于A的aop 代理对象,此时A 已经在一级中了,那么线程2边能从map1 中获取到 对象A,(当然你也可以对整个创建过程加锁,那么就不存在多线程问题)。但是此时A 对象并没有完成赋值与init 的方法执行,这样很容易造成线程2 的异常,所以还需要一层缓存将完成整个创建过程的bean ,与没有创建完成的纯洁bean 隔离开,所以才有了3级缓存。

此时再考虑3级缓存的获取,基于并发的考虑就不难理解第一个方法为何要synchronized 了,即保证并发下,缓存只在一层缓存中存在。

综上来说,我的理解,aop 问题需要两层缓存来解决,而考虑多线程并发的情况下,所以需要三层缓存来解决。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 详解spring注解式参数校验

    详解spring注解式参数校验

    本篇文章主要介绍了详解spring注解式参数校验,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • JavaScript中的isTrusted属性及其应用场景详解

    JavaScript中的isTrusted属性及其应用场景详解

    在现代 Web 开发中,JavaScript 是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件处理、数据传递和状态管理等,本文将通过一个实际案例,深入探讨 isTrusted 属性的来源、作用,需要的朋友可以参考下
    2025-01-01
  • java中long和Long有什么区别详解

    java中long和Long有什么区别详解

    这篇文章主要介绍了Java中long和Long是基本数据类型和包装数据类型的区别,包括默认值、内存占用、使用场景、方法支持以及装箱和拆箱,包装数据类型如Integer提供了许多有用的方法,需要的朋友可以参考下
    2025-02-02
  • SpringBoot创建自定义starter详解

    SpringBoot创建自定义starter详解

    这篇文章主要介绍了SpringBoot创建自定义starter详解,Starter是Spring Boot中的一个非常重要的概念,Starter相当于模块,它能将模块所需的依赖整合起来并对模块内的Bean根据环境(条件)进行自动配置,需要的朋友可以参考下
    2024-01-01
  • Java解析XML文件开源库DOM4J

    Java解析XML文件开源库DOM4J

    dom4j是一个Java的XML API,是jdom的升级品,用来读写XML文件的。dom4j是一个十分优秀的JavaXML API,具有性能优异、功能强大和极其易使用的特点,它的性能超过sun公司官方的dom技术,同时它也是一个开放源代码的软件
    2023-01-01
  • 详细聊聊Mybatis中万能的Map

    详细聊聊Mybatis中万能的Map

    最近有个需求,就是使用mybatis时,向mysql中插入数据,其参数为map类型,下面这篇文章主要给大家介绍了关于Mybatis中万能的Map的相关资料,需要的朋友可以参考下
    2021-12-12
  • 彻底解决Spring mvc中时间的转换和序列化等问题

    彻底解决Spring mvc中时间的转换和序列化等问题

    这篇文章主要介绍了彻底解决Spring mvc中时间的转换和序列化等问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • Java中File与byte[]的互转方式

    Java中File与byte[]的互转方式

    这篇文章主要介绍了Java中File与byte[]的互转方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • springboot2.1.3配置sftp自定义sftp连接池的详细过程

    springboot2.1.3配置sftp自定义sftp连接池的详细过程

    这篇文章主要介绍了springboot2.1.3配置sftp自定义sftp连接池的详细过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08
  • java实现操作系统的短进程作业调度示例分享

    java实现操作系统的短进程作业调度示例分享

    java编写的实现了操作系统中的短作业进程,可以实现几道作业同时作业调度
    2014-02-02

最新评论