原文博客:Doi技術(shù)團隊
鏈接地址:https://blog.doiduoyi.com
初心:記錄優(yōu)秀的Doi技術(shù)團隊學(xué)習(xí)經(jīng)歷
本系列介紹
本系列《剖析緩存系列》,由淺到深的對緩存進行分析介紹,從緩存形式,更新策略,常見問題,以及JAVA緩存使用(JCache,Spring cache,Ehcache)和緩存服務(wù)器redis
系列目錄
本章
本章分為兩篇《熟悉JSR-107 JAVA規(guī)范》和《剖析JCache》。
《熟悉JSR-107 JAVA緩存規(guī)范》偏向熟悉JAVA緩存規(guī)范,JAVA緩存使用。
《剖析JCache》 重點講解高級用法,監(jiān)聽器、資源加載、實現(xiàn)源碼、注解使用等。
什么是JSR-107
JSR是Java Specification Requests的縮寫,意思是Java 規(guī)范提案。2012年10月26日J(rèn)SR規(guī)范委員會發(fā)布了JSR 107(JCache API的首個早期草案。
JCache規(guī)范定義了一種對Java對象臨時在內(nèi)存中進行緩存的方法,包括對象的創(chuàng)建、共享訪問、假脫機(spooling)、失效、各JVM的一致性等,可被用于緩存JSP內(nèi)最經(jīng)常讀取的數(shù)據(jù)。
JSR-107定義JAVA緩存概念
JSR-107 介紹了一些JAVA緩存應(yīng)該具有的一些能力,存儲方式以及存儲結(jié)構(gòu)的概念。
如下:
- JAVA緩存的一些能力
- 值存儲和引用存儲
- 緩存和Map
JAVA緩存的一些能力
- 提供一些特殊的緩存能力。例如緩存JAVA對象
- 定義出通用的抽象類和工具。例如:spring的
Cache
和CacheManager
這兩個接口 - 簡單易用
- 對業(yè)務(wù)的侵略性低
- 提供進程內(nèi)和分布式的緩存實現(xiàn)
- 支持按值或者引用來緩存數(shù)據(jù)
- 支持注解來實現(xiàn)緩存功能
值存儲和引用存儲
- 值存儲:每次獲取緩存都會深拷貝一份,以至于修改值不會有副作用
- 通常通過序列化的方式實現(xiàn)(因此值存儲的對象需要實現(xiàn)Serializable接口)
- 任何不是操作緩存的方式修改key,value都不會對緩存有任何影響,例如:cache.put(key,value);key=123; 此key并不會影響到緩存
- 引用存儲:共用維護一份緩存,修改值會產(chǎn)生副作用
- 任何對該引用對象發(fā)生改變,都會影響到該k-v存儲的緩存,那么就會出現(xiàn)無法獲取或者無法移除的問題。
- 引用存儲只適用于存儲在本地堆中,如果對緩存進行移除,需要將其引用轉(zhuǎn)為其他的表示形式(表示已移除),以至于往后的任何操作都不會影響到緩存。
其實這兩種方式是語義學(xué),在實際開發(fā)中,值存儲也有可能發(fā)生值引用的問題,例如 存儲的是一個對象,對象里如果有參數(shù)是引用,那么也會引用到同一個對象。
緩存和Map的區(qū)別
緩存的存儲結(jié)構(gòu)是一個類似Map的,但是由于緩存的使用場景,導(dǎo)致其又與Map有差異性
相同點
- 通過key存儲數(shù)據(jù)
- key唯一
- 使用可變對象作為key需要特別小心
- 比較相等可以重寫equals和hash方法
差異性
- 緩存存儲的key和values都不能為空
- 緩存可以過期
- 空間不足的時候,某些緩存有可能會被刪除
- 當(dāng)內(nèi)存空間不足,可以運行某些策略釋放一些舊的緩存,例如:LRU策略
- 實現(xiàn)CAS操作,需要實現(xiàn)equals方法
- 序列化keys和values
- 緩存可以選擇不同的存儲實體,以及不同的存儲類型(值存儲或者存儲引用)
- 可以校驗安全性,拋出異常
引入JCache
在maven項目中引入以下兩個項目cache-api
和cache-ri-impl
cache-api
是在javax包下。javax,也叫java拓展包是java標(biāo)準(zhǔn)的一部分,但是沒有包含在標(biāo)準(zhǔn)庫中,一般屬于標(biāo)準(zhǔn)庫的擴展。通常屬于某個特定領(lǐng)域,不是一般性的api。
cache-ri-impl
,該api全稱叫 Cache Reference Implementation。這個包是專門按JCache規(guī)范實現(xiàn)的,不推薦商業(yè)使用。也就是說該包只是用于學(xué)習(xí)用途,讓開發(fā)者了解實現(xiàn)方式以及向開發(fā)者解釋規(guī)范想要表達的思想。通常這些包都是開源的,由制定API或者規(guī)范的人所編寫。github源碼通常帶著包名是xxx-ri的包都稱為RI,這些RI包按照API或者規(guī)范實現(xiàn)的,不建議商業(yè)用途。這些都是用作如何實現(xiàn)API或者規(guī)范得實例。下面這段話是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.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.jsr107.ri</groupId>
<artifactId>cache-ri-impl</artifactId>
<version>1.1.1</version>
</dependency>
項目模塊
JavaCache(簡稱JCache)定義了Java標(biāo)準(zhǔn)的api。JCache主要定義了5個接口來規(guī)范緩存的生命周期
5個接口如下:
- CacheingProvider:管理多個CacheManager,制定建立,配置,請求機制
- CacheManager:管理多個Cache,制定建立,配置,請求機制,只有一個對應(yīng)的CacheProvider
- Cache:對緩存操作,只有一個對應(yīng)的CacheManager
- Cache.Entry:Cache接口的內(nèi)部接口,真正的存儲實體
- ExporyPolicy:控制緩存的過期時間。
如圖:5個核心接口和一個CachIng工具類(用于生產(chǎn)默認(rèn)的CacheingProvider)
簡單的Demo
@Test
public void simpleCache() {
//創(chuàng)建一個緩存管理器
CacheManager manager = Caching.getCachingProvider().getCacheManager();
//創(chuàng)建一個配置管理器
Configuration<Integer, String> configuration = new MutableConfiguration<Integer, String>().setTypes(Integer.class, String.class);
//生成一個緩存對象
Cache<Integer, String> simpleCache = manager.getCache("simpleCache22");
//緩存數(shù)據(jù)
simpleCache = manager.createCache("simpleCache22", configuration);
simpleCache.put(2, "value");
//獲取數(shù)據(jù)
String value = simpleCache.get(2);
System.out.println("Value: " + value);
}
Caching類
Caching類時javax提供的一個工具類,為了方便開發(fā)者去獲取合適的CachingProvider實例的(該接口的實現(xiàn)類是管理CacheManager的生命周期)。該Caching類大致提供了3種獲取CachingProvider實例的方式
- 獲取默認(rèn)的CachingProvider
- 根據(jù)ClassLoader獲取CachingProvider
- 根據(jù)全類名創(chuàng)建/獲取開發(fā)者實現(xiàn)的實例
源碼解析
從三個方面解析實現(xiàn)類Caching:
- 存儲CachingProvider的數(shù)據(jù)結(jié)構(gòu)
- 根據(jù)ClassLoader創(chuàng)建/獲取CachingProvider實例
- 根據(jù)全類名創(chuàng)建/獲取開發(fā)者實現(xiàn)的實例
存儲CachingProvider的數(shù)據(jù)結(jié)構(gòu)
通過WeakHashMap
存儲CacheManager
- 先根據(jù)ClassLoader作為key,value是HashMap<URI, CacheManager>
- 再由URI作為key,值才是真正的CacheManager
存儲結(jié)構(gòu)如下:
this.cachingProviders = new WeakHashMap<ClassLoader, LinkedHashMap<String,CachingProvider>>();
創(chuàng)建/獲取CachingProvider實例
- 該類有兩種模式,一種是只創(chuàng)建一個CachingProvider,一種是創(chuàng)建多個CachingProvider
- 如果系統(tǒng)配置有JAVAX_CACHE_CACHING_PROVIDER,那么只會創(chuàng)建一個CachingProvider
- 可以從源碼看出確定一個CachingProvider是以類名確定
- 根據(jù)全類名創(chuàng)建/獲取開發(fā)者實現(xiàn)的實例就是調(diào)用loadCachingProvider(String fullyQualifiedClassName, ClassLoader classLoader)
關(guān)鍵源碼如下
public synchronized Iterable<CachingProvider> getCachingProviders(ClassLoader classLoader) {
//如果傳入的ClassLoader為空,會拿當(dāng)前線程的ClassLoader作為ClassLoader
final ClassLoader serviceClassLoader = classLoader == null ? getDefaultClassLoader() : classLoader;
//根據(jù)ClassLoader從存儲CachingProvider的WeakHashMap獲取
//獲取到的是LinkedHashMap<String, CachingProvider>類型的map,其實String作為key是ClassLoader.name
LinkedHashMap<String, CachingProvider> providers = cachingProviders.get(serviceClassLoader);
if (providers == null) {
// 創(chuàng)建方式1
//如果系統(tǒng)配置有JAVAX_CACHE_CACHING_PROVIDER,那么就直接以JAVAX_CACHE_CACHING_PROVIDER為類名,
//調(diào)用loadCachingProvider()方法創(chuàng)建一個CachingProvider
//為了限制只創(chuàng)建一個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 {
// 創(chuàng)建方式1
//創(chuàng)建多個CachingProvider
LinkedHashMap<String, CachingProvider> result = new LinkedHashMap<String, CachingProvider>();
//調(diào)用 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();
}
//根據(jù)全量名確定生成一個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);
}
}
}
CachingProvider
該類是緩存核心接口。這個接口的實現(xiàn)類提供創(chuàng)建和管理CacheManager
生命周期的方法??梢酝ㄟ^java.net.URI和ClassLoader創(chuàng)建一個唯一CacheManager
實例,通常會使用java.net.URI去創(chuàng)建一個唯一CacheManager
實例,
該接口定義了以下方法
public interface CachingProvider extends Closeable {
//通過**java.net.URI**和**ClassLoader**創(chuàng)建/獲取一個唯一``CacheManager``實例,Propertiess是對`CacheManager``的配置。
CacheManager getCacheManager(URI uri, ClassLoader classLoader,Properties properties);
//獲取默認(rèn)的DefaultClassLoader
ClassLoader getDefaultClassLoader();
//獲取默認(rèn)的DefaultURI
URI getDefaultURI();
//獲取默認(rèn)的Properties
Properties getDefaultProperties();
//通過**java.net.URI**和**ClassLoader**創(chuàng)建/獲取一個唯一``CacheManager``實例、
CacheManager getCacheManager(URI uri, ClassLoader classLoader);
//根據(jù)默認(rèn)的DefaultURI和DefaultClassLoader創(chuàng)建/獲取一個唯一`CacheManager``實例
CacheManager getCacheManager();
//關(guān)閉所有的``CacheManager``實例以及相關(guān)的資源
void close();
void close(ClassLoader classLoader);
void close(URI uri, ClassLoader classLoader);
//用于確定**CachingProvider**實現(xiàn)類是否支持某個操作
boolean isSupported(OptionalFeature optionalFeature);
}
源碼解析
本文主要是解析org.jsr107.ri包的源碼。如下,查看的是org.jsr107.ri.spi包下的CachingProvider接口實現(xiàn)類RICachingProvider
從三個方面解析實現(xiàn)類RICachingProvider:
- 存儲CacheManager的數(shù)據(jù)結(jié)構(gòu)
- 創(chuàng)建/獲取CacheManager實例
- 銷毀CacheManager實例
存儲CacheManager的數(shù)據(jù)結(jié)構(gòu)
CachingProvider
內(nèi)部是通過WeakHashMap
存儲CacheManager。
- 先根據(jù)ClassLoader作為key,value是HashMap<URI, CacheManager>
- 再由URI作為key,值才是真正的CacheManager
這樣子做的目的是為了可以讓ClassLoad和URI確定一個CacheManager.(一個CachingProvider可以創(chuàng)建多個不同的CacheManager實例)
存儲結(jié)構(gòu)如下:
WeakHashMap<ClassLoader, HashMap<URI, CacheManager>> cacheManagersByClassLoader
創(chuàng)建CacheManager
- 可以通過
getCacheManager()
直接獲取默認(rèn)的CacheManager - 可以通過統(tǒng)一資源標(biāo)識符 (URI) 獲取指定CacheManager
- 可以通過
ClassLoader
獲取指定CacheManager
部分代碼如下:
public synchronized CacheManager getCacheManager(URI uri, ClassLoader classLoader, Properties properties) {
// 構(gòu)建URI,如果傳參是null,就用默認(rèn)的
URI managerURI = uri == null ? getDefaultURI() : uri;
//構(gòu)建ClassLoader 如果傳參是Null,就用默認(rèn)的
ClassLoader managerClassLoader = classLoader == null ? getDefaultClassLoader() : classLoader;
//根據(jù)ClassLoader獲取CacheManager
HashMap<URI, CacheManager> cacheManagersByURI = cacheManagersByClassLoader.get(managerClassLoader);
//根據(jù)URI獲取CacheManager
CacheManager cacheManager = cacheManagersByURI.get(managerURI);
}
為什么搞這么復(fù)雜用URI和ClassLoader來確定同一個緩存?
因為為了在分布式情況下,共用同一個URI就可以定位到同一個緩存,從而達到共享緩存
ClassLoader主要想可以將CacheManager劃分不同的職責(zé),例如:OrderClassLoader 主要是訂單功能用的CacheManager
銷毀CacheManager實例
CachingProvider是創(chuàng)建和管理CacheManager實例的生命周期,自然也負責(zé)銷毀它,提供了3個方法用于銷毀自己管理的CacheManager實例
- 銷毀其管理的所有CacheManager實例
- 銷毀其管理的由某個ClassLoader加載的CacheManager實例
- 銷毀其管理的某個ClassLoader和某個URI確定的唯一CacheManager實例
銷毀其管理的所有CacheManager實例的源碼如下:
其實銷毀邏輯并不在CachingProvider中實現(xiàn),而是交給每個CacheManager實例去實現(xiàn)銷毀
public synchronized void close() {
//獲取當(dāng)前管理的所有CacheManager
WeakHashMap<ClassLoader, HashMap<URI, CacheManager>> managersByClassLoader = this.cacheManagersByClassLoader;
//創(chuàng)建一個新的WeakHashMap用于存儲新的CacheManager
this.cacheManagersByClassLoader = new WeakHashMap<ClassLoader, HashMap<URI, CacheManager>>();
//逐一調(diào)用每個cacheManager的close方法
for (ClassLoader classLoader : managersByClassLoader.keySet()) {
for (CacheManager cacheManager : managersByClassLoader.get(classLoader).values()) {
cacheManager.close();
}
}
}
創(chuàng)建CachingProvider
CachingProvider是由javax.cache.Caching
創(chuàng)建的
創(chuàng)建方式跟創(chuàng)建CacheManager
類似
- 可以通過
java.util.ServiceLoader
創(chuàng)建實例(ServiceLoader是JAVA提供的一個可以加載META-INF/services目錄下的全類名,以這些類名創(chuàng)建provider實例,并以類名作為key) - 可以通過默認(rèn)方法
Caching.getCachingProvider()
創(chuàng)建/獲取默認(rèn)實例 - 可以通過全類名創(chuàng)建實例(簡化了第一種方法,直接以提供的全類名去創(chuàng)建provider實例)
ServiceLoader創(chuàng)建CachingProvider部分源碼如下:
public synchronized Iterable<CachingProvider> getCachingProviders(ClassLoader classLoader) {
//獲取classLoader
final ClassLoader serviceClassLoader = classLoader == null ? getDefaultClassLoader() : classLoader;
LinkedHashMap<String, CachingProvider> result = new LinkedHashMap<String, CachingProvider>();
//調(diào)用ServiceLoader去創(chuàng)建CachingProvider
ServiceLoader<CachingProvider> serviceLoader = ServiceLoader.load(CachingProvider.class, serviceClassLoader);
for (CachingProvider provider : serviceLoader) {
result.put(provider.getClass().getName(), provider);
}
}
CacheManager
CacheManger
是一個接口,主要提供創(chuàng)建,配置,獲取,關(guān)閉和銷毀緩存的方法。
- 同一個CacheManager管理的緩存,由同一個基礎(chǔ)結(jié)構(gòu)創(chuàng)建而成的,例如同一個
ClassLoader
和同一個properties
(同一套配置),因為ClassLoader
和properties
都是由CachingProvider管理的。 - 同樣的,也可以共同分享外部資源,例如存儲在同一個存儲空間里。
- 可以通過
CacheProvider
獲取默認(rèn)的CacheManager
實現(xiàn)實例。 - 所有異常都會拋出
IllegalStateException
異常,例如:對關(guān)閉的緩存進行訪問和操作,配置校驗不正確
CacheManager
提供的方法:
- 建立和配置一個唯一名稱的緩存對象
- 根據(jù)唯一名稱獲取緩存對象
- 銷毀緩存
- 獲取其綁定的
CacheProvider
對象 - 等等
源碼如下:
public interface CacheManager extends Closeable {
// 獲取唯一的CachingProvider
CachingProvider getCachingProvider();
//獲取URI,CacheManager的唯一標(biāo)識,用于識別唯一資源
URI getURI();
//獲取CacheManager的ClassLoader,用于加載資源
ClassLoader getClassLoader();
//獲取Properties對象,該對象用于生成Cache時的配置
Properties getProperties();
//創(chuàng)建緩存
<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();
//根據(jù)名稱銷毀緩存
void destroyCache(String cacheName);
//是否啟動管理
void enableManagement(String cacheName, boolean enabled);
//是否開啟統(tǒng)計
void enableStatistics(String cacheName, boolean enabled);
//銷毀當(dāng)前CacheManager管理的所有緩存
void close();
boolean isClosed();
<T> T unwrap(java.lang.Class<T> clazz);
}
Configuration配置緩存
Javax提供了2個接口(Configuration
,CompleteConfiguration
)和一個實現(xiàn)類(MutableConfiguration
)完成對cache的配置。
類圖如下:
Configuration
接口
從類圖可以看到Configuration
接口是父接口,其主要定義了Cache的k-v的類型。k-v都是對象類型,那么比較k-v就可以通過重寫hashCode() 和 equlas() 這兩個方法。該接口也定義一個isStoreByValue() 方法,用于判斷該Cache 是值存儲還是引用存儲,如果為false,那么key和value都是引用存儲。true是值存儲。默認(rèn)是值存儲。值存儲一般通過序列化實現(xiàn),因此存儲對象需要實現(xiàn)Serialized接口。
值存儲也有可能引發(fā)值引用的問題,當(dāng)值存儲的對象中有參數(shù)是引用,那么就會有這種問題。
CompleteConfiguration
接口
該接口是用于配置javax.cache.Cache類的。如果開發(fā)者是自定義實現(xiàn)緩存的操作類(類似javax.cache.Cache),那么只需要實現(xiàn)Configuration
接口即可。
也就是說,javax對Cache增加了一些配置,這些配置都由CompleteConfiguration
接口統(tǒng)一管理了。那么開發(fā)者要自定義緩存操作類添加自定義配置,應(yīng)該創(chuàng)建一個與CompleteConfiguration
接口一樣性質(zhì)的接口
接口源碼如下:
public interface CompleteConfiguration<K, V> extends Configuration<K, V>,
Serializable {
//是否開啟read-through 模式,當(dāng)緩存不存在的時候會采取策略。在第一章緩存介紹里有介紹這種模式
boolean isReadThrough();
//是否開啟write-through 模式,當(dāng)更新緩存的時候會采取策略。在第一章緩存介紹里有介紹這種模式
boolean isWriteThrough();
//是否開啟統(tǒng)計信息收集
boolean isStatisticsEnabled();
//是否開啟管理緩存,這里指的管理是類似CacheMXBean的管理
boolean isManagementEnabled();
//緩存監(jiān)聽器,用于監(jiān)聽緩存的創(chuàng)建,創(chuàng)建,過期,異常操作
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提供的,用于實現(xiàn)CompleteConfiguration
接口的配置類。通常開發(fā)者可以直接使用此類來完成緩存的配置。
該類的具體源碼下文在使用的時候會分析
Cache
接口
這是剖析的第四個接口。這個接口定義對緩存的操作,因此會緩存的操作實現(xiàn)類應(yīng)該繼承此接口。Cache
的內(nèi)部接口Entry<k,v>
才是定義緩存存儲對象的接口。
這個接口是核心,很多邏輯都是在此接口定義的方法中觸發(fā)的,例如put() 方法,會觸發(fā)CacheEntryCreatedListener事件,如果是更新操作,還會觸發(fā)CacheEntryUpdatedListener事件,如果是舊值過期還有觸發(fā)CacheEntryExpiredListener事件。源碼的剖析會在下文細講。
類圖如下:
內(nèi)部接口Entry
類似Map存儲結(jié)構(gòu)的臨時存儲對象。這種存儲結(jié)構(gòu):父接口定義操作方法,內(nèi)部接口定義存儲數(shù)據(jù)格式使用非常廣泛。例如Map接口使用了這種存儲結(jié)構(gòu)。
我們可以看一下org.jsr107.ri的實現(xiàn):
可看到RIEntry實現(xiàn)了內(nèi)部類Cache.Entry,并且增加多了一個參數(shù)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
,這個接口并沒有提供任何的過期策略,只是定義了對緩存分別做創(chuàng)建,獲取,更新操作的時候返回一個Duration
對象的操作的方法。(ExpiryPolicy
的實現(xiàn)類可以通過這三個接口,獲取Duration,通過Duration對象處理時間)
JCahce提供了5個過期策略
CreateExpiryPolicy
: 創(chuàng)建緩存的時候設(shè)置一個過期時間
ModifiedExpiryPolicy
:更新緩存的時候刷新過期時間
AccessExpiryPolicy
:獲取緩存的時候刷新過期時間
TouchedExpiryPolicy
:獲取緩存和更新緩存都會刷新緩存時間
EternalExpiryPolicy
:默認(rèn)策略,沒有過期時間
類圖如下:
Duration
類
該類可以看做是時間長度類,通常創(chuàng)建后就不會改變,用來計算時長后的時間。ExpiryPolicy
的實現(xiàn)類內(nèi)部就是使用這個對象來對緩存進行時間的操作。
[圖片上傳失敗...(image-b69c95-1566521123177)]
執(zhí)行策略流程
一個簡單的demo:
public void simpleAPITypeEnforcement() {
//獲取默認(rèn)的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)) //設(shè)置過期時間 使用AccessExpiryPolicy
.setStatisticsEnabled(true);
//創(chuàng)建緩存
cacheManager.createCache("simpleOptionalCache", config);
//獲取到上面創(chuàng)建的緩存
Cache<String, Integer> cache = Caching.getCache("simpleOptionalCache",
String.class, Integer.class);
//使用緩存,存儲數(shù)據(jù)
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,該策略是在獲取緩存的時候執(zhí)行過期策略。那么接下來,從策略的配置到策略的觸發(fā)整個流程看一下。
大致流程分為:
- 過期策略的配置
- 創(chuàng)建緩存
- 存儲數(shù)據(jù)
- 獲取緩存的時候刷新過期時間
- 過期策略的配置
過期策略的配置是Configuration實現(xiàn)類上進行,MutableConfiguration<String, Integer> 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
是一個定義時間長度的類。該方法是創(chuàng)建一個工廠,該工廠用于管理AccessedExpiryPolicy
實例。
為什么說管理而不是創(chuàng)建,因為該工廠的create()方法,只是返回了一個實例而已,這個實例就是上面源碼new AccessedExpiryPolicy(duration)
創(chuàng)建的實例。
- 創(chuàng)建緩存
接下來我們看下創(chuàng)建緩存的時候,發(fā)生了什么。cacheManager.createCache("simpleOptionalCache", config);
,通過該方法創(chuàng)建緩存,那內(nèi)部源碼如下。
源碼來源于org.jsr107.ri的實現(xiàn)類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)
這個方法中,只是創(chuàng)建了一個RICache
對象,并存儲到一個Map中,并沒有出現(xiàn)配置邏輯。那么配置邏輯應(yīng)該是在RICache
的構(gòu)造方法上。接下來看一下RICache
的構(gòu)造方法
RICache(RICacheManager cacheManager,
String cacheName,
ClassLoader classLoader,
Configuration<K, V> configuration) {
//省略其他源碼
expiryPolicy = this.configuration.getExpiryPolicyFactory().create();
}
從該構(gòu)造方法可以看到,調(diào)用配置類中的工廠create()
方法,獲取過期策略的實例。然后將其賦值給RICache
實例。
- 存儲緩存
在這里,我們要觀察當(dāng)我們存儲數(shù)據(jù)的時候,過期策略是如果實現(xiàn)的。以下是通過剖析RICache
的put(k v)
方法源碼
public void put(K key, V value) {
//省略其他源碼
//創(chuàng)建一個存儲實體對象
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
對象,這個對象上文提到過,是記錄時間長度的類。如果發(fā)出異常,那么就到了2,會通過getDefaultDuration()
返回一個空的Duration
對象。
3就是調(diào)用getAdjustedTime
方法計算過期時間,源碼如下
public long getAdjustedTime(long time) {
//如果是空的,返回最大值
if (isEternal()) {
return Long.MAX_VALUE;
} else {
//傳入時間加上時間長度
return time + timeUnit.toMillis(durationAmount);
}
}
由于傳入的是當(dāng)前時間,所以得到的expiryTime就是當(dāng)前時間后的該Duration
對象的配置的時間長度。(上文demo是設(shè)置的是1個小時)
- 獲取緩存的時候刷新過期時間
因為AccessExpiryPolicy
類的過期策略是:獲取緩存的時候刷新過期時間。所以我們可以直接在get()
方法中查找它的實現(xiàn)邏輯。
部分源碼如下:
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策略 那么執(zhí)行該策略
if (duration != null) {
long expiryTime = duration.getAdjustedTime(now);
cachedValue.setExpiryTime(expiryTime);
}
}
總結(jié)
本篇介紹了JAVA緩存的一些概念和規(guī)范。介紹了4個核心接口:CacheingProvider
,CacheManager
,Cache
,ExporyPolicy
和一個工具類Caching
。