关于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 问题需要两层缓存来解决,而考虑多线程并发的情况下,所以需要三层缓存来解决。

总结

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

相关文章

  • 23种设计模式(8) java外观模式

    23种设计模式(8) java外观模式

    这篇文章主要为大家详细介绍了23种设计模式之java外观模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • Spring中属性注入的几种方式以及复杂属性的注入详解

    Spring中属性注入的几种方式以及复杂属性的注入详解

    这篇文章主要介绍了Spring中属性注入的几种方式以及复杂属性的注入详解,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • 数据库基本操作语法归纳总结

    数据库基本操作语法归纳总结

    本篇文章主要介绍了数据库的一些常用方法及一些基本操作,需要的朋友可以参考下
    2017-04-04
  • Java中包装类和Arrays类的详细介绍

    Java中包装类和Arrays类的详细介绍

    Arrays针对于数组做操作的类,该类包含用于操作数组的各种方法(如排序和搜索),这篇文章主要给大家介绍了关于Java中包装类和Arrays类的详细介绍,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • java计算任意位水仙花数示例(回文数)

    java计算任意位水仙花数示例(回文数)

    这篇文章主要介绍了java计算任意位水仙花数示例(回文数),需要的朋友可以参考下
    2014-05-05
  • Java cookie和session会话技术介绍

    Java cookie和session会话技术介绍

    session的工作原理和cookie非常类似,在cookie中存放一个sessionID,真实的数据存放在服务器端,客户端每次发送请求的时候带上sessionID,服务端根据sessionID进行数据的响应
    2023-04-04
  • 五分钟带你快速学习Spring IOC

    五分钟带你快速学习Spring IOC

    这篇文章主要给大家介绍了关于如何通过五分钟快速学习Spring IOC的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-03-03
  • Flutter实现文本组件、图标及按钮组件的代码

    Flutter实现文本组件、图标及按钮组件的代码

    这篇文章主要介绍了Flutter实现文本组件、图标及按钮组件的代码,本文给大家介绍的非常详细,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-07-07
  • java的java.security.egd源码解读

    java的java.security.egd源码解读

    这篇文章主要为大家介绍了java的java.security.egd源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Java面试最容易被刷的重难点之锁的使用策略

    Java面试最容易被刷的重难点之锁的使用策略

    锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂。因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字
    2021-10-10

最新评论