本系列《剖析缓存系列》,由浅到深的对缓存进行分析介绍,从缓存形式,更新策略,常见问题,以及JAVA缓存使用(JCache,Spring cache,Ehcache)和缓存服务器redis
本章分为两篇《熟悉JSR-107 JAVA规范》和《剖析JCache》。
《熟悉JSR-107 JAVA缓存规范》偏向熟悉JAVA缓存规范,JAVA缓存使用。
《剖析JCache》 重点讲解高级用法,监听器、资源加载、实现源码、注解使用等。
JSR是Java Specification Requests的缩写,意思是Java 规范提案。2012年10月26日JSR规范委员会发布了JSR 107(JCache API的首个早期草案。
JCache规范定义了一种对Java对象临时在内存中进行缓存的方法,包括对象的创建、共享访问、假脱机(spooling)、失效、各JVM的一致性等,可被用于缓存JSP内最经常读取的数据。
JSR-107 介绍了一些JAVA缓存应该具有的一些能力,存储方式以及存储结构的概念。
如下:
Cache
和CacheManager
这两个接口其实这两种方式是语义学,在实际开发中,值存储也有可能发生值引用的问题,例如 存储的是一个对象,对象里如果有参数是引用,那么也会引用到同一个对象。
缓存的存储结构是一个类似Map的,但是由于缓存的使用场景,导致其又与Map有差异性
在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.
<dependency>
<groupId>javax.cachegroupId>
<artifactId>cache-apiartifactId>
<version>1.1.0version>
dependency>
<dependency>
<groupId>org.jsr107.rigroupId>
<artifactId>cache-ri-implartifactId>
<version>1.1.1version>
dependency>
JavaCache(简称JCache)定义了Java标准的api。JCache主要定义了5个接口来规范缓存的生命周期
5个接口如下:
如图:5个核心接口和一个CachIng工具类(用于生产默认的CacheingProvider)
@Test
public void simpleCache() {
//创建一个缓存管理器
CacheManager manager = Caching.getCachingProvider().getCacheManager();
//创建一个配置管理器
Configuration<Integer, String> configuration = new MutableConfiguration<Integer, String>().setTypes(Integer.class, String.class);
//生成一个缓存对象
Cache<Integer, String> simpleCache = manager.getCache("simpleCache22");
//缓存数据
simpleCache = manager.createCache("simpleCache22", configuration);
simpleCache.put(2, "value");
//获取数据
String value = simpleCache.get(2);
System.out.println("Value: " + value);
}
Caching类时javax提供的一个工具类,为了方便开发者去获取合适的CachingProvider实例的(该接口的实现类是管理CacheManager的生命周期)。该Caching类大致提供了3种获取CachingProvider实例的方式
从三个方面解析实现类Caching:
- 存储CachingProvider的数据结构
- 根据ClassLoader创建/获取CachingProvider实例
- 根据全类名创建/获取开发者实现的实例
通过WeakHashMap
存储CacheManager
this.cachingProviders = new WeakHashMap<ClassLoader, LinkedHashMap<String,CachingProvider>>();
关键源码如下
public synchronized Iterable<CachingProvider> getCachingProviders(ClassLoader classLoader) {
//如果传入的ClassLoader为空,会拿当前线程的ClassLoader作为ClassLoader
final ClassLoader serviceClassLoader = classLoader == null ? getDefaultClassLoader() : classLoader;
//根据ClassLoader从存储CachingProvider的WeakHashMap获取
//获取到的是LinkedHashMap类型的map,其实String作为key是ClassLoader.name
LinkedHashMap<String, CachingProvider> 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<String, CachingProvider>();
providers.put(className, loadCachingProvider(className, serviceClassLoader));
} else {
// 创建方式1
//创建多个CachingProvider
LinkedHashMap<String, CachingProvider> result = new LinkedHashMap<String, CachingProvider>();
//调用 ServiceLoader去加载类,ServiceLoader是java.util下的用于加载META-INF/services目录下的配置文件
ServiceLoader<CachingProvider> 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<CachingProvider>) 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);
}
}
}
该类是缓存核心接口。这个接口的实现类提供创建和管理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实例
CachingProvider
内部是通过WeakHashMap
存储CacheManager。
WeakHashMap<ClassLoader, HashMap<URI, CacheManager>> cacheManagersByClassLoader
getCacheManager()
直接获取默认的CacheManagerClassLoader
获取指定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<URI, CacheManager> cacheManagersByURI = cacheManagersByClassLoader.get(managerClassLoader);
//根据URI获取CacheManager
CacheManager cacheManager = cacheManagersByURI.get(managerURI);
}
为什么搞这么复杂用URI和ClassLoader来确定同一个缓存?
因为为了在分布式情况下,共用同一个URI就可以定位到同一个缓存,从而达到共享缓存
ClassLoader主要想可以将CacheManager划分不同的职责,例如:OrderClassLoader 主要是订单功能用的CacheManager
CachingProvider是创建和管理CacheManager实例的生命周期,自然也负责销毁它,提供了3个方法用于销毁自己管理的CacheManager实例
销毁其管理的所有CacheManager实例的源码如下:
其实销毁逻辑并不在CachingProvider中实现,而是交给每个CacheManager实例去实现销毁
public synchronized void close() {
//获取当前管理的所有CacheManager
WeakHashMap<ClassLoader, HashMap<URI, CacheManager>> managersByClassLoader = this.cacheManagersByClassLoader;
//创建一个新的WeakHashMap用于存储新的CacheManager
this.cacheManagersByClassLoader = new WeakHashMap<ClassLoader, HashMap<URI, CacheManager>>();
//逐一调用每个cacheManager的close方法
for (ClassLoader classLoader : managersByClassLoader.keySet()) {
for (CacheManager cacheManager : managersByClassLoader.get(classLoader).values()) {
cacheManager.close();
}
}
}
CachingProvider是由javax.cache.Caching
创建的
创建方式跟创建CacheManager
类似
java.util.ServiceLoader
创建实例(ServiceLoader是JAVA提供的一个可以加载META-INF/services目录下的全类名,以这些类名创建provider实例,并以类名作为key)Caching.getCachingProvider()
创建/获取默认实例ServiceLoader创建CachingProvider部分源码如下:
public synchronized Iterable<CachingProvider> getCachingProviders(ClassLoader classLoader) {
//获取classLoader
final ClassLoader serviceClassLoader = classLoader == null ? getDefaultClassLoader() : classLoader;
LinkedHashMap<String, CachingProvider> result = new LinkedHashMap<String, CachingProvider>();
//调用ServiceLoader去创建CachingProvider
ServiceLoader<CachingProvider> serviceLoader = ServiceLoader.load(CachingProvider.class, serviceClassLoader);
for (CachingProvider provider : serviceLoader) {
result.put(provider.getClass().getName(), provider);
}
}
CacheManger
是一个接口,主要提供创建,配置,获取,关闭和销毁缓存的方法。
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();
//创建缓存
<K, V, C extends Configuration<K, V>> Cache<K, V> createCache(String cacheName,C configuration)throws IllegalArgumentException;
//获取缓存
<K, V> Cache<K, V> getCache(String cacheName, Class<K> keyType,Class<V> valueType);
<K, V> Cache<K, V> getCache(String cacheName);
Iterable<String> getCacheNames();
//根据名称销毁缓存
void destroyCache(String cacheName);
//是否启动管理
void enableManagement(String cacheName, boolean enabled);
//是否开启统计
void enableStatistics(String cacheName, boolean enabled);
//销毁当前CacheManager管理的所有缓存
void close();
boolean isClosed();
<T> T unwrap(java.lang.Class<T> clazz);
}
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<K, V> extends Configuration<K, V>,
Serializable {
//是否开启read-through 模式,当缓存不存在的时候会采取策略。在第一章缓存介绍里有介绍这种模式
boolean isReadThrough();
//是否开启write-through 模式,当更新缓存的时候会采取策略。在第一章缓存介绍里有介绍这种模式
boolean isWriteThrough();
//是否开启统计信息收集
boolean isStatisticsEnabled();
//是否开启管理缓存,这里指的管理是类似CacheMXBean的管理
boolean isManagementEnabled();
//缓存监听器,用于监听缓存的创建,创建,过期,异常操作
Iterable<CacheEntryListenerConfiguration<K,
V>> getCacheEntryListenerConfigurations();
//缓存加载处理工厂,通常在read-through中使用
Factory<CacheLoader<K, V>> getCacheLoaderFactory();
//缓存更新处理工厂 常在write-through中使用
Factory<CacheWriter<? super K, ? super V>> getCacheWriterFactory();
//缓存过期处理工厂 用于缓存过期时的处理
Factory<ExpiryPolicy> getExpiryPolicyFactory();
}
MutableConfiguration
类:该类是javax.cache提供的,用于实现CompleteConfiguration
接口的配置类。通常开发者可以直接使用此类来完成缓存的配置。
该类的具体源码下文在使用的时候会分析
Cache
接口这是剖析的第四个接口。这个接口定义对缓存的操作,因此会缓存的操作实现类应该继承此接口。Cache
的内部接口Entry
才是定义缓存存储对象的接口。
这个接口是核心,很多逻辑都是在此接口定义的方法中触发的,例如put() 方法,会触发CacheEntryCreatedListener事件,如果是更新操作,还会触发CacheEntryUpdatedListener事件,如果是旧值过期还有触发CacheEntryExpiredListener事件。源码的剖析会在下文细讲。
类似Map存储结构的临时存储对象。这种存储结构:父接口定义操作方法,内部接口定义存储数据格式使用非常广泛。例如Map接口使用了这种存储结构。
我们可以看一下org.jsr107.ri的实现:
可看到RIEntry实现了内部类Cache.Entry,并且增加多了一个参数oldValue,用于更新缓存的时候,保留旧的缓存value
public class RIEntry<K, V> implements Cache.Entry<K, V> {
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<String, Integer> config = new MutableConfiguration<>();
//使用值引用存储
config.setStoreByValue(false)
.setTypes(String.class, Integer.class)
.setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(ONE_HOUR)) //设置过期时间 使用AccessExpiryPolicy
.setStatisticsEnabled(true);
//创建缓存
cacheManager.createCache("simpleOptionalCache", config);
//获取到上面创建的缓存
Cache<String, Integer> 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,该策略是在获取缓存的时候执行过期策略。那么接下来,从策略的配置到策略的触发整个流程看一下。
大致流程分为:
MutableConfiguration config; config.setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(ONE_HOUR))
。我们可以看看AccessedExpiryPolicy.factoryOf(Duration)
是个什么方法。public static Factory<ExpiryPolicy> factoryOf(Duration duration) {
return new FactoryBuilder.SingletonFactory<ExpiryPolicy>(new AccessedExpiryPolicy(duration));
}
可以看到 AccessedExpiryPolicy.factoryOf(Duration)
传入的是Duration
对象,上文提到Duration
是一个定义时间长度的类。该方法是创建一个工厂,该工厂用于管理AccessedExpiryPolicy
实例。
为什么说管理而不是创建,因为该工厂的create()方法,只是返回了一个实例而已,这个实例就是上面源码new AccessedExpiryPolicy(duration)
创建的实例。
cacheManager.createCache("simpleOptionalCache", config);
,通过该方法创建缓存,那内部源码如下。源码来源于org.jsr107.ri的实现类RICache
public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(String cacheName, C configuration) {
cache = new RICache(this, cacheName, getClassLoader(), configuration);
caches.put(cache.getName(), cache);
return (Cache<K, V>) cache;
}
从createCache(String,C)
这个方法中,只是创建了一个RICache
对象,并存储到一个Map中,并没有出现配置逻辑。那么配置逻辑应该是在RICache
的构造方法上。接下来看一下RICache
的构造方法
RICache(RICacheManager cacheManager,
String cacheName,
ClassLoader classLoader,
Configuration<K, V> configuration) {
//省略其他源码
expiryPolicy = this.configuration.getExpiryPolicyFactory().create();
}
从该构造方法可以看到,调用配置类中的工厂create()
方法,获取过期策略的实例。然后将其赋值给RICache
实例。
RICache
的put(k v)
方法源码public void put(K key, V value) {
//省略其他源码
//创建一个存储实体对象
RIEntry<K, V> entry = new RIEntry<K, V>(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
。