原文博客:Doi技术团队
链接地址:https://blog.doiduoyi.com
初心:记录优秀的Doi技术团队学习经历
本系列介绍
本系列《剖析缓存系列》,由浅到深的对缓存进行分析介绍,从缓存形式,更新策略,常见问题,以及JAVA缓存使用(JCache,Spring cache,Ehcache)和缓存服务器redis
系列目录
本章
本章分为两篇《熟悉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的
Cache
和CacheManager
这两个接口 - 简单易用
- 对业务的侵略性低
- 提供进程内和分布式的缓存实现
- 支持按值或者引用来缓存数据
- 支持注解来实现缓存功能
值存储和引用存储
- 值存储:每次获取缓存都会深拷贝一份,以至于修改值不会有副作用
- 通常通过序列化的方式实现(因此值存储的对象需要实现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-api
和cache-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)
简单的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实例的方式
- 获取默认的CachingProvider
- 根据ClassLoader获取CachingProvider
- 根据全类名创建/获取开发者实现的实例
源码解析
从三个方面解析实现类Caching:
- 存储CachingProvider的数据结构
- 根据ClassLoader创建/获取CachingProvider实例
- 根据全类名创建/获取开发者实现的实例
存储CachingProvider的数据结构
通过WeakHashMap
存储CacheManager
- 先根据ClassLoader作为key,value是HashMap
- 再由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.URI和ClassLoader创建一个唯一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:
- 存储CacheManager的数据结构
- 创建/获取CacheManager实例
- 销毁CacheManager实例
存储CacheManager的数据结构
CachingProvider
内部是通过WeakHashMap
存储CacheManager。
- 先根据ClassLoader作为key,value是HashMap
- 再由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
(同一套配置),因为ClassLoader
和properties
都是由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的配置。
类图如下:
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事件。源码的剖析会在下文细讲。
类图如下:
内部接口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
:默认策略,没有过期时间
类图如下:
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,该策略是在获取缓存的时候执行过期策略。那么接下来,从策略的配置到策略的触发整个流程看一下。
大致流程分为:
- 过期策略的配置
- 创建缓存
- 存储数据
- 获取缓存的时候刷新过期时间
- 过期策略的配置
过期策略的配置是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)
创建的实例。
- 创建缓存
接下来我们看下创建缓存的时候,发生了什么。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
实例。
- 存储缓存
在这里,我们要观察当我们存储数据的时候,过期策略是如果实现的。以下是通过剖析RICache
的put(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个小时)
- 获取缓存的时候刷新过期时间
因为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
。