《剖析缓存系列》—— 熟悉JSR-107 JAVA缓存规范

原文博客:Doi技术团队
链接地址:https://blog.doiduoyi.com
初心:记录优秀的Doi技术团队学习经历

本系列介绍

本系列《剖析缓存系列》,由浅到深的对缓存进行分析介绍,从缓存形式,更新策略,常见问题,以及JAVA缓存使用(JCache,Spring cache,Ehcache)和缓存服务器redis

系列目录

思维导图.jpg

本章

本章分为两篇《熟悉JSR-107 JAVA规范》和《剖析JCache》。
《熟悉JSR-107 JAVA缓存规范》偏向熟悉JAVA缓存规范,JAVA缓存使用。
《剖析JCache》 重点讲解高级用法,监听器、资源加载、实现源码、注解使用等。

什么是JSR-107

JSR是Java Specification Requests的缩写,意思是Java 规范提案。2012年10月26日JSR规范委员会发布了JSR 107(JCache API的首个早期草案。
JCache规范定义了一种对Java对象临时在内存中进行缓存的方法,包括对象的创建、共享访问、假脱机(spooling)、失效、各JVM的一致性等,可被用于缓存JSP内最经常读取的数据。

JSR-107定义JAVA缓存概念

JSR-107 介绍了一些JAVA缓存应该具有的一些能力,存储方式以及存储结构的概念。
如下:

  • JAVA缓存的一些能力
  • 值存储和引用存储
  • 缓存和Map

JAVA缓存的一些能力

  • 提供一些特殊的缓存能力。例如缓存JAVA对象
  • 定义出通用的抽象类和工具。例如:spring的CacheCacheManager这两个接口
  • 简单易用
  • 对业务的侵略性低
  • 提供进程内和分布式的缓存实现
  • 支持按值或者引用来缓存数据
  • 支持注解来实现缓存功能

值存储和引用存储

  • 值存储:每次获取缓存都会深拷贝一份,以至于修改值不会有副作用
    • 通常通过序列化的方式实现(因此值存储的对象需要实现Serializable接口)
    • 任何不是操作缓存的方式修改key,value都不会对缓存有任何影响,例如:cache.put(key,value);key=123; 此key并不会影响到缓存
  • 引用存储:共用维护一份缓存,修改值会产生副作用
    • 任何对该引用对象发生改变,都会影响到该k-v存储的缓存,那么就会出现无法获取或者无法移除的问题。
    • 引用存储只适用于存储在本地堆中,如果对缓存进行移除,需要将其引用转为其他的表示形式(表示已移除),以至于往后的任何操作都不会影响到缓存。

其实这两种方式是语义学,在实际开发中,值存储也有可能发生值引用的问题,例如 存储的是一个对象,对象里如果有参数是引用,那么也会引用到同一个对象。

缓存和Map的区别

缓存的存储结构是一个类似Map的,但是由于缓存的使用场景,导致其又与Map有差异性

相同点
  • 通过key存储数据
  • key唯一
  • 使用可变对象作为key需要特别小心
  • 比较相等可以重写equals和hash方法
差异性
  • 缓存存储的key和values都不能为空
  • 缓存可以过期
  • 空间不足的时候,某些缓存有可能会被删除
    • 当内存空间不足,可以运行某些策略释放一些旧的缓存,例如:LRU策略
  • 实现CAS操作,需要实现equals方法
  • 序列化keys和values
  • 缓存可以选择不同的存储实体,以及不同的存储类型(值存储或者存储引用)
  • 可以校验安全性,抛出异常

引入JCache

在maven项目中引入以下两个项目cache-apicache-ri-impl

cache-api是在javax包下。javax,也叫java拓展包是java标准的一部分,但是没有包含在标准库中,一般属于标准库的扩展。通常属于某个特定领域,不是一般性的api。

cache-ri-impl,该api全称叫 Cache Reference Implementation。这个包是专门按JCache规范实现的,不推荐商业使用。也就是说该包只是用于学习用途,让开发者了解实现方式以及向开发者解释规范想要表达的思想。通常这些包都是开源的,由制定API或者规范的人所编写。github源码

通常带着包名是xxx-ri的包都称为RI,这些RI包按照API或者规范实现的,不建议商业用途。这些都是用作如何实现API或者规范得实例。下面这段话是RI的定义
A Reference Implementation is an implementation (usually Open Source as a proprietary one wouldn't make much sense) of an API or specification. These are meant to be used as an example of how an API/Spec could be implemented. Usually written by the developers of the API/Spec. Examples of such are Glassfish 3 as the implementation of the Java EE 6 specification.

        
            javax.cache
            cache-api
            1.1.0
        
        
            org.jsr107.ri
            cache-ri-impl
            1.1.1
        

项目模块

JavaCache(简称JCache)定义了Java标准的api。JCache主要定义了5个接口来规范缓存的生命周期
5个接口如下:

  • CacheingProvider:管理多个CacheManager,制定建立,配置,请求机制
  • CacheManager:管理多个Cache,制定建立,配置,请求机制,只有一个对应的CacheProvider
  • Cache:对缓存操作,只有一个对应的CacheManager
  • Cache.Entry:Cache接口的内部接口,真正的存储实体
  • ExporyPolicy:控制缓存的过期时间。

如图:5个核心接口和一个CachIng工具类(用于生产默认的CacheingProvider)


《剖析缓存系列》—— 熟悉JSR-107 JAVA缓存规范_第1张图片
01.png

简单的Demo

    @Test
    public void simpleCache() {

        //创建一个缓存管理器
        CacheManager manager = Caching.getCachingProvider().getCacheManager();
        //创建一个配置管理器
        Configuration configuration = new MutableConfiguration().setTypes(Integer.class, String.class);
        //生成一个缓存对象
        Cache simpleCache = manager.getCache("simpleCache22");
        //缓存数据
        simpleCache = manager.createCache("simpleCache22", configuration);
        simpleCache.put(2, "value");
        //获取数据
        String value = simpleCache.get(2);
        System.out.println("Value: " + value);
    }

Caching类

Caching类时javax提供的一个工具类,为了方便开发者去获取合适的CachingProvider实例的(该接口的实现类是管理CacheManager的生命周期)。该Caching类大致提供了3种获取CachingProvider实例的方式

  1. 获取默认的CachingProvider
  2. 根据ClassLoader获取CachingProvider
  3. 根据全类名创建/获取开发者实现的实例

源码解析

从三个方面解析实现类Caching

  1. 存储CachingProvider的数据结构
  2. 根据ClassLoader创建/获取CachingProvider实例
  3. 根据全类名创建/获取开发者实现的实例
存储CachingProvider的数据结构

通过WeakHashMap存储CacheManager

  1. 先根据ClassLoader作为key,value是HashMap
  2. 再由URI作为key,值才是真正的CacheManager
    存储结构如下:
 this.cachingProviders = new WeakHashMap>();
创建/获取CachingProvider实例
  • 该类有两种模式,一种是只创建一个CachingProvider,一种是创建多个CachingProvider
  • 如果系统配置有JAVAX_CACHE_CACHING_PROVIDER,那么只会创建一个CachingProvider
  • 可以从源码看出确定一个CachingProvider是以类名确定
  • 根据全类名创建/获取开发者实现的实例就是调用loadCachingProvider(String fullyQualifiedClassName, ClassLoader classLoader)

关键源码如下

public synchronized Iterable getCachingProviders(ClassLoader classLoader) {
      //如果传入的ClassLoader为空,会拿当前线程的ClassLoader作为ClassLoader
      final ClassLoader serviceClassLoader = classLoader == null ? getDefaultClassLoader() : classLoader;
      //根据ClassLoader从存储CachingProvider的WeakHashMap获取
      //获取到的是LinkedHashMap类型的map,其实String作为key是ClassLoader.name
      LinkedHashMap providers = cachingProviders.get(serviceClassLoader);

      if (providers == null) {
        // 创建方式1
        //如果系统配置有JAVAX_CACHE_CACHING_PROVIDER,那么就直接以JAVAX_CACHE_CACHING_PROVIDER为类名,
        //调用loadCachingProvider()方法创建一个CachingProvider
        
        //为了限制只创建一个CachingProvider
        if (System.getProperties().containsKey(JAVAX_CACHE_CACHING_PROVIDER)) {
          String className = System.getProperty(JAVAX_CACHE_CACHING_PROVIDER);
          providers = new LinkedHashMap();
          providers.put(className, loadCachingProvider(className, serviceClassLoader));

        } else {
                // 创建方式1
               //创建多个CachingProvider
              LinkedHashMap result = new LinkedHashMap();
                //调用 ServiceLoader去加载类,ServiceLoader是java.util下的用于加载META-INF/services目录下的配置文件
              ServiceLoader serviceLoader = ServiceLoader.load(CachingProvider.class, serviceClassLoader);
              //将ServiceLoader加载到的CachingProvider存储到map中
              for (CachingProvider provider : serviceLoader) {
                result.put(provider.getClass().getName(), provider);
              }
              return result;
        }
        //以ClassLoader为key存储到Caching管理的map中
        cachingProviders.put(serviceClassLoader, providers);
      }
      return providers.values();
}


//根据全量名确定生成一个CachingProvider
protected CachingProvider loadCachingProvider(String fullyQualifiedClassName, ClassLoader classLoader) throws CacheException {
      synchronized (classLoader) {
        try {
          Class clazz = classLoader.loadClass(fullyQualifiedClassName);
          if (CachingProvider.class.isAssignableFrom(clazz)) {
            return ((Class) clazz).newInstance();
          } else {
            throw new CacheException("The specified class [" + fullyQualifiedClassName + "] is not a CachingProvider");
          }
        } catch (Exception e) {
          throw new CacheException("Failed to load the CachingProvider [" + fullyQualifiedClassName + "]", e);
        }
      }
    }

CachingProvider

该类是缓存核心接口。这个接口的实现类提供创建和管理CacheManager生命周期的方法。可以通过java.net.URIClassLoader创建一个唯一CacheManager实例,通常会使用java.net.URI去创建一个唯一CacheManager实例,

该接口定义了以下方法

public interface CachingProvider extends Closeable {
   //通过**java.net.URI**和**ClassLoader**创建/获取一个唯一``CacheManager``实例,Propertiess是对`CacheManager``的配置。
  CacheManager getCacheManager(URI uri, ClassLoader classLoader,Properties properties);
  //获取默认的DefaultClassLoader
  ClassLoader getDefaultClassLoader();
 //获取默认的DefaultURI
  URI getDefaultURI();
  //获取默认的Properties
  Properties getDefaultProperties();
   //通过**java.net.URI**和**ClassLoader**创建/获取一个唯一``CacheManager``实例、
  CacheManager getCacheManager(URI uri, ClassLoader classLoader);
 //根据默认的DefaultURI和DefaultClassLoader创建/获取一个唯一`CacheManager``实例
  CacheManager getCacheManager();
 //关闭所有的``CacheManager``实例以及相关的资源
  void close();

  void close(ClassLoader classLoader);

  void close(URI uri, ClassLoader classLoader);
  //用于确定**CachingProvider**实现类是否支持某个操作
  boolean isSupported(OptionalFeature optionalFeature);
}

源码解析

本文主要是解析org.jsr107.ri包的源码。如下,查看的是org.jsr107.ri.spi包下的CachingProvider接口实现类RICachingProvider
从三个方面解析实现类RICachingProvider

  1. 存储CacheManager的数据结构
  2. 创建/获取CacheManager实例
  3. 销毁CacheManager实例
存储CacheManager的数据结构

CachingProvider内部是通过WeakHashMap存储CacheManager。

  1. 先根据ClassLoader作为key,value是HashMap
  2. 再由URI作为key,值才是真正的CacheManager
    这样子做的目的是为了可以让ClassLoad和URI确定一个CacheManager.(一个CachingProvider可以创建多个不同的CacheManager实例)
    存储结构如下:
WeakHashMap> cacheManagersByClassLoader
创建CacheManager
  • 可以通过getCacheManager()直接获取默认的CacheManager
  • 可以通过统一资源标识符 (URI) 获取指定CacheManager
  • 可以通过ClassLoader获取指定CacheManager
    部分代码如下:
 public synchronized CacheManager getCacheManager(URI uri, ClassLoader classLoader, Properties properties) {
    // 构建URI,如果传参是null,就用默认的
    URI managerURI = uri == null ? getDefaultURI() : uri;
    //构建ClassLoader 如果传参是Null,就用默认的
    ClassLoader managerClassLoader = classLoader == null ? getDefaultClassLoader() : classLoader;
    //根据ClassLoader获取CacheManager
    HashMap cacheManagersByURI = cacheManagersByClassLoader.get(managerClassLoader);
    //根据URI获取CacheManager
    CacheManager cacheManager = cacheManagersByURI.get(managerURI);
}

为什么搞这么复杂用URI和ClassLoader来确定同一个缓存?
因为为了在分布式情况下,共用同一个URI就可以定位到同一个缓存,从而达到共享缓存
ClassLoader主要想可以将CacheManager划分不同的职责,例如:OrderClassLoader 主要是订单功能用的CacheManager

销毁CacheManager实例

CachingProvider是创建和管理CacheManager实例的生命周期,自然也负责销毁它,提供了3个方法用于销毁自己管理的CacheManager实例

  • 销毁其管理的所有CacheManager实例
  • 销毁其管理的由某个ClassLoader加载的CacheManager实例
  • 销毁其管理的某个ClassLoader和某个URI确定的唯一CacheManager实例

销毁其管理的所有CacheManager实例的源码如下:
其实销毁逻辑并不在CachingProvider中实现,而是交给每个CacheManager实例去实现销毁

public synchronized void close() {
    //获取当前管理的所有CacheManager
    WeakHashMap> managersByClassLoader = this.cacheManagersByClassLoader;
    //创建一个新的WeakHashMap用于存储新的CacheManager
    this.cacheManagersByClassLoader = new WeakHashMap>();
    //逐一调用每个cacheManager的close方法
    for (ClassLoader classLoader : managersByClassLoader.keySet()) {
      for (CacheManager cacheManager : managersByClassLoader.get(classLoader).values()) {
        cacheManager.close();
      }
    }
}

创建CachingProvider

CachingProvider是由javax.cache.Caching创建的
创建方式跟创建CacheManager类似

  • 可以通过java.util.ServiceLoader创建实例(ServiceLoader是JAVA提供的一个可以加载META-INF/services目录下的全类名,以这些类名创建provider实例,并以类名作为key)
  • 可以通过默认方法Caching.getCachingProvider()创建/获取默认实例
  • 可以通过全类名创建实例(简化了第一种方法,直接以提供的全类名去创建provider实例)

ServiceLoader创建CachingProvider部分源码如下:

public synchronized Iterable getCachingProviders(ClassLoader classLoader) {

        //获取classLoader
      final ClassLoader serviceClassLoader = classLoader == null ? getDefaultClassLoader() : classLoader;

      LinkedHashMap result = new LinkedHashMap();

        //调用ServiceLoader去创建CachingProvider
      ServiceLoader serviceLoader = ServiceLoader.load(CachingProvider.class, serviceClassLoader);
      for (CachingProvider provider : serviceLoader) {
        result.put(provider.getClass().getName(), provider);
      }      
    }

CacheManager

CacheManger是一个接口,主要提供创建,配置,获取,关闭和销毁缓存的方法。

  • 同一个CacheManager管理的缓存,由同一个基础结构创建而成的,例如同一个ClassLoader和同一个properties (同一套配置),因为ClassLoaderproperties 都是由CachingProvider管理的。
  • 同样的,也可以共同分享外部资源,例如存储在同一个存储空间里。
  • 可以通过CacheProvider获取默认的CacheManager实现实例。
  • 所有异常都会抛出IllegalStateException异常,例如:对关闭的缓存进行访问和操作,配置校验不正确

CacheManager提供的方法:

  • 建立和配置一个唯一名称的缓存对象
  • 根据唯一名称获取缓存对象
  • 销毁缓存
  • 获取其绑定的CacheProvider对象
  • 等等
    源码如下:
public interface CacheManager extends Closeable {
 
 // 获取唯一的CachingProvider
  CachingProvider getCachingProvider();
//获取URI,CacheManager的唯一标识,用于识别唯一资源
  URI getURI();
//获取CacheManager的ClassLoader,用于加载资源
  ClassLoader getClassLoader();
//获取Properties对象,该对象用于生成Cache时的配置
  Properties getProperties();
//创建缓存
  > Cache createCache(String cacheName,C configuration)throws IllegalArgumentException;
//获取缓存
   Cache getCache(String cacheName, Class keyType,Class valueType);

   Cache getCache(String cacheName);

  Iterable getCacheNames();
//根据名称销毁缓存
  void destroyCache(String cacheName);
//是否启动管理
  void enableManagement(String cacheName, boolean enabled);
 //是否开启统计
  void enableStatistics(String cacheName, boolean enabled);
//销毁当前CacheManager管理的所有缓存
  void close();

  boolean isClosed();

   T unwrap(java.lang.Class clazz);
}

Configuration配置缓存

Javax提供了2个接口(Configuration,CompleteConfiguration)和一个实现类(MutableConfiguration)完成对cache的配置。
类图如下:

《剖析缓存系列》—— 熟悉JSR-107 JAVA缓存规范_第2张图片
02.png

Configuration接口

从类图可以看到Configuration接口是父接口,其主要定义了Cache的k-v的类型。k-v都是对象类型,那么比较k-v就可以通过重写hashCode()equlas() 这两个方法。该接口也定义一个isStoreByValue() 方法,用于判断该Cache 是值存储还是引用存储,如果为false,那么key和value都是引用存储。true是值存储。默认是值存储。值存储一般通过序列化实现,因此存储对象需要实现Serialized接口。
值存储也有可能引发值引用的问题,当值存储的对象中有参数是引用,那么就会有这种问题。

CompleteConfiguration 接口

该接口是用于配置javax.cache.Cache类的。如果开发者是自定义实现缓存的操作类(类似javax.cache.Cache),那么只需要实现Configuration接口即可。
也就是说,javax对Cache增加了一些配置,这些配置都由CompleteConfiguration 接口统一管理了。那么开发者要自定义缓存操作类添加自定义配置,应该创建一个与CompleteConfiguration 接口一样性质的接口

接口源码如下:

public interface CompleteConfiguration extends Configuration,
    Serializable {
//是否开启read-through 模式,当缓存不存在的时候会采取策略。在第一章缓存介绍里有介绍这种模式
  boolean isReadThrough();
//是否开启write-through 模式,当更新缓存的时候会采取策略。在第一章缓存介绍里有介绍这种模式
  boolean isWriteThrough();

//是否开启统计信息收集
  boolean isStatisticsEnabled();

//是否开启管理缓存,这里指的管理是类似CacheMXBean的管理
  boolean isManagementEnabled();

//缓存监听器,用于监听缓存的创建,创建,过期,异常操作
  Iterable> getCacheEntryListenerConfigurations();
//缓存加载处理工厂,通常在read-through中使用 
  Factory> getCacheLoaderFactory();
//缓存更新处理工厂  常在write-through中使用 
  Factory> getCacheWriterFactory();
//缓存过期处理工厂  用于缓存过期时的处理
  Factory getExpiryPolicyFactory();

}

MutableConfiguration类:

该类是javax.cache提供的,用于实现CompleteConfiguration 接口的配置类。通常开发者可以直接使用此类来完成缓存的配置。

该类的具体源码下文在使用的时候会分析

Cache接口

这是剖析的第四个接口。这个接口定义对缓存的操作,因此会缓存的操作实现类应该继承此接口。Cache的内部接口Entry才是定义缓存存储对象的接口。
这个接口是核心,很多逻辑都是在此接口定义的方法中触发的,例如put() 方法,会触发CacheEntryCreatedListener事件,如果是更新操作,还会触发CacheEntryUpdatedListener事件,如果是旧值过期还有触发CacheEntryExpiredListener事件。源码的剖析会在下文细讲。

类图如下:


《剖析缓存系列》—— 熟悉JSR-107 JAVA缓存规范_第3张图片
03.png
内部接口Entry

类似Map存储结构的临时存储对象。这种存储结构:父接口定义操作方法,内部接口定义存储数据格式使用非常广泛。例如Map接口使用了这种存储结构。
我们可以看一下org.jsr107.ri的实现:
可看到RIEntry实现了内部类Cache.Entry,并且增加多了一个参数oldValue,用于更新缓存的时候,保留旧的缓存value

public class RIEntry implements Cache.Entry {
  private final K key;
  private final V value;
  private final V oldValue;
}

类型安全

为了保证运行安全,Configuration配置类中可以配置cache的k-v类型,在CacheManager也提供了getCache(name,kType,vType)的方法获取指定的缓存类型(如果类型不正确,抛出IllegalStateException异常)

ExpiryPolicy接口

提供了一个接口ExpiryPolicy,这个接口并没有提供任何的过期策略,只是定义了对缓存分别做创建,获取,更新操作的时候返回一个Duration对象的操作的方法。(ExpiryPolicy的实现类可以通过这三个接口,获取Duration,通过Duration对象处理时间)

JCahce提供了5个过期策略
CreateExpiryPolicy: 创建缓存的时候设置一个过期时间
ModifiedExpiryPolicy:更新缓存的时候刷新过期时间
AccessExpiryPolicy:获取缓存的时候刷新过期时间
TouchedExpiryPolicy:获取缓存和更新缓存都会刷新缓存时间
EternalExpiryPolicy:默认策略,没有过期时间

类图如下:


《剖析缓存系列》—— 熟悉JSR-107 JAVA缓存规范_第4张图片
04.png
Duration

该类可以看做是时间长度类,通常创建后就不会改变,用来计算时长后的时间。ExpiryPolicy的实现类内部就是使用这个对象来对缓存进行时间的操作。
[图片上传失败...(image-b69c95-1566521123177)]

执行策略流程

一个简单的demo:

public void simpleAPITypeEnforcement() {

    //获取默认的cacheManager
    CachingProvider cachingProvider = Caching.getCachingProvider();
    CacheManager cacheManager = cachingProvider.getCacheManager();

    //配置缓存
    MutableConfiguration config = new MutableConfiguration<>();
    //使用值引用存储
    config.setStoreByValue(false)
        .setTypes(String.class, Integer.class)
        .setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(ONE_HOUR))   //设置过期时间 使用AccessExpiryPolicy
        .setStatisticsEnabled(true);

    //创建缓存
    cacheManager.createCache("simpleOptionalCache", config);

    //获取到上面创建的缓存
    Cache cache = Caching.getCache("simpleOptionalCache",
        String.class, Integer.class);

    //使用缓存,存储数据
    String key = "key";
    Integer value1 = 1;
    cache.put("key", value1);
    Integer value2 = cache.get(key);
    assertEquals(value1, value2);

    cache.remove("key");
    assertNull(cache.get("key"));
  }

上面的demo,使用了过期策略AccessExpiryPolicy,该策略是在获取缓存的时候执行过期策略。那么接下来,从策略的配置到策略的触发整个流程看一下。

大致流程分为:

  • 过期策略的配置
  • 创建缓存
  • 存储数据
  • 获取缓存的时候刷新过期时间
  1. 过期策略的配置
    过期策略的配置是Configuration实现类上进行,MutableConfiguration config; config.setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(ONE_HOUR))。我们可以看看AccessedExpiryPolicy.factoryOf(Duration)是个什么方法。
public static Factory factoryOf(Duration duration) {
    return new FactoryBuilder.SingletonFactory(new AccessedExpiryPolicy(duration));
  }

可以看到 AccessedExpiryPolicy.factoryOf(Duration)传入的是Duration对象,上文提到Duration是一个定义时间长度的类。该方法是创建一个工厂,该工厂用于管理AccessedExpiryPolicy实例。
为什么说管理而不是创建,因为该工厂的create()方法,只是返回了一个实例而已,这个实例就是上面源码new AccessedExpiryPolicy(duration)创建的实例。

  1. 创建缓存
    接下来我们看下创建缓存的时候,发生了什么。cacheManager.createCache("simpleOptionalCache", config);,通过该方法创建缓存,那内部源码如下。

源码来源于org.jsr107.ri的实现类RICache

public > Cache createCache(String cacheName, C configuration) {
        cache = new RICache(this, cacheName, getClassLoader(), configuration);
        caches.put(cache.getName(), cache);
        return (Cache) cache;
}

createCache(String,C)这个方法中,只是创建了一个RICache对象,并存储到一个Map中,并没有出现配置逻辑。那么配置逻辑应该是在RICache的构造方法上。接下来看一下RICache的构造方法

 RICache(RICacheManager cacheManager,
          String cacheName,
          ClassLoader classLoader,
          Configuration configuration) {  
          //省略其他源码
           expiryPolicy = this.configuration.getExpiryPolicyFactory().create();
}

从该构造方法可以看到,调用配置类中的工厂create()方法,获取过期策略的实例。然后将其赋值给RICache实例。

  1. 存储缓存
    在这里,我们要观察当我们存储数据的时候,过期策略是如果实现的。以下是通过剖析RICacheput(k v)方法源码
public void put(K key, V value) {
        //省略其他源码
        //创建一个存储实体对象
        RIEntry entry = new RIEntry(key, value);
        Duration duration;
        try {
        //1
          duration = expiryPolicy.getExpiryForCreation();
        } catch (Throwable t) {
        //2
          duration = getDefaultDuration();
        }
        //3
        long expiryTime = duration.getAdjustedTime(now);
        //4
        cachedValue = new RICachedValue(internalValue, now, expiryTime);
}

由以上源码的1可以看到从过期策略类中获取到Duration对象,这个对象上文提到过,是记录时间长度的类。如果发出异常,那么就到了2,会通过getDefaultDuration()返回一个空的Duration对象。
3就是调用getAdjustedTime方法计算过期时间,源码如下

  public long getAdjustedTime(long time) {
  //如果是空的,返回最大值
    if (isEternal()) {
      return Long.MAX_VALUE;
    } else {
    //传入时间加上时间长度
      return time + timeUnit.toMillis(durationAmount);
    }
  }

由于传入的是当前时间,所以得到的expiryTime就是当前时间后的该Duration对象的配置的时间长度。(上文demo是设置的是1个小时)

  1. 获取缓存的时候刷新过期时间
    因为AccessExpiryPolicy类的过期策略是:获取缓存的时候刷新过期时间。所以我们可以直接在get()方法中查找它的实现逻辑。
    部分源码如下:
       cachedValue = entries.get(internalKey);

     boolean isExpired = cachedValue != null && cachedValue.isExpiredAt(now);
    //如果缓存已过期或者为空
     if (cachedValue == null || isExpired) {
       //省略部分源码
       return null;
     } else {
     //获取缓存的值
       value = valueConverter.fromInternal(cachedValue.getInternalValue(now));
       try {
       //获取Access策略
         Duration duration = expiryPolicy.getExpiryForAccess();
         //如果有配置Access策略 那么执行该策略
         if (duration != null) {
           long expiryTime = duration.getAdjustedTime(now);
           cachedValue.setExpiryTime(expiryTime);
         }
       }

总结

本篇介绍了JAVA缓存的一些概念和规范。介绍了4个核心接口:CacheingProvider,CacheManager,Cache,ExporyPolicy和一个工具类Caching

你可能感兴趣的:(《剖析缓存系列》—— 熟悉JSR-107 JAVA缓存规范)