Caffeine Cache~高性能 Java 本地缓存之王
前面剛說到Guava Cache,他的優點是封裝了get,put操作;提供線程安全的緩存操作;提供過期策略;提供回收策略;緩存監控。當緩存的數據超過最大值時,使用LRU算法替換。這一篇我們將要談到一個新的本地緩存框架:Caffeine Cache。它也是站在巨人的肩膀上-Guava Cache,借著他的思想優化了算法發展而來。
本篇博文主要介紹Caffine Cache 的使用方式,以及Caffine Cache在SpringBoot中的使用。
1. Caffine Cache 在算法上的優點-W-TinyLFU
說到優化,Caffine Cache到底優化了什么呢?我們剛提到過LRU,常見的緩存淘汰算法還有FIFO,LFU:
FIFO:先進先出,在這種淘汰算法中,先進入緩存的會先被淘汰,會導致命中率很低。
LRU:最近最少使用算法,每次訪問數據都會將其放在我們的隊尾,如果需要淘汰數據,就只需要淘汰隊首即可。仍然有個問題,如果有個數據在 1 分鐘訪問了 1000次,再后 1 分鐘沒有訪問這個數據,但是有其他的數據訪問,就導致了我們這個熱點數據被淘汰。
LFU:最近最少頻率使用,利用額外的空間記錄每個數據的使用頻率,然后選出頻率最低進行淘汰。這樣就避免了 LRU 不能處理時間段的問題。
上面三種策略各有利弊,實現的成本也是一個比一個高,同時命中率也是一個比一個好。Guava Cache雖然有這么多的功能,但是本質上還是對LRU的封裝,如果有更優良的算法,并且也能提供這么多功能,相比之下就相形見絀了。
LFU的局限性?:在 LFU 中只要數據訪問模式的概率分布隨時間保持不變時,其命中率就能變得非常高。比如有部新劇出來了,我們使用 LFU 給他緩存下來,這部新劇在這幾天大概訪問了幾億次,這個訪問頻率也在我們的 LFU 中記錄了幾億次。但是新劇總會過氣的,比如一個月之后這個新劇的前幾集其實已經過氣了,但是他的訪問量的確是太高了,其他的電視劇根本無法淘汰這個新劇,所以在這種模式下是有局限性。
LRU的優點和局限性?:LRU可以很好的應對突發流量的情況,因為他不需要累計數據頻率。但LRU通過歷史數據來預測未來是局限的,它會認為最后到來的數據是最可能被再次訪問的,從而給與它最高的優先級。
在現有算法的局限性下,會導致緩存數據的命中率或多或少的受損,而命中略又是緩存的重要指標。HighScalability網站刊登了一篇文章,由前Google工程師發明的W-TinyLFU——一種現代的緩存 。Caffine Cache就是基于此算法而研發。Caffeine 因使用?Window TinyLfu?回收策略,提供了一個近乎最佳的命中率?。
“當數據的訪問模式不隨時間變化的時候,LFU的策略能夠帶來最佳的緩存命中率。然而LFU有兩個缺點:
首先,它需要給每個記錄項維護頻率信息,每次訪問都需要更新,這是個巨大的開銷;
其次,如果數據訪問模式隨時間有變,LFU的頻率信息無法隨之變化,因此早先頻繁訪問的記錄可能會占據緩存,而后期訪問較多的記錄則無法被命中。
因此,大多數的緩存設計都是基于LRU或者其變種來進行的。相比之下,LRU并不需要維護昂貴的緩存記錄元信息,同時也能夠反應隨時間變化的數據訪問模式。然而,在許多負載之下,LRU依然需要更多的空間才能做到跟LFU一致的緩存命中率。因此,一個“現代”的緩存,應當能夠綜合兩者的長處。
TinyLFU維護了近期訪問記錄的頻率信息,作為一個過濾器,當新記錄來時,只有滿足TinyLFU要求的記錄才可以被插入緩存。如前所述,作為現代的緩存,它需要解決兩個挑戰:
一個是如何避免維護頻率信息的高開銷;
另一個是如何反應隨時間變化的訪問模式。
首先來看前者,TinyLFU借助了數據流Sketching技術,Count-Min Sketch顯然是解決這個問題的有效手段,它可以用小得多的空間存放頻率信息,而保證很低的False Positive Rate。但考慮到第二個問題,就要復雜許多了,因為我們知道,任何Sketching數據結構如果要反應時間變化都是一件困難的事情,在Bloom Filter方面,我們可以有Timing Bloom Filter,但對于CMSketch來說,如何做到Timing CMSketch就不那么容易了。TinyLFU采用了一種基于滑動窗口的時間衰減設計機制,借助于一種簡易的reset操作:每次添加一條記錄到Sketch的時候,都會給一個計數器上加1,當計數器達到一個尺寸W的時候,把所有記錄的Sketch數值都除以2,該reset操作可以起到衰減的作用 。
W-TinyLFU主要用來解決一些稀疏的突發訪問元素。在一些數目很少但突發訪問量很大的場景下,TinyLFU將無法保存這類元素,因為它們無法在給定時間內積累到足夠高的頻率。因此W-TinyLFU就是結合LFU和LRU,前者用來應對大多數場景,而LRU用來處理突發流量。
在處理頻率記錄的方案中,你可能會想到用hashMap去存儲,每一個key對應一個頻率值。那如果數據量特別大的時候,是不是這個hashMap也會特別大呢。由此可以聯想到 Bloom Filter,對于每個key,用n個byte每個存儲一個標志用來判斷key是否在集合中。原理就是使用k個hash函數來將key散列成一個整數。
在W-TinyLFU中使用Count-Min Sketch記錄我們的訪問頻率,而這個也是布隆過濾器的一種變種。如下圖所示:
如果需要記錄一個值,那我們需要通過多種Hash算法對其進行處理hash,然后在對應的hash算法的記錄中+1,為什么需要多種hash算法呢?由于這是一個壓縮算法必定會出現沖突,比如我們建立一個byte的數組,通過計算出每個數據的hash的位置。比如張三和李四,他們兩有可能hash值都是相同,比如都是1那byte[1]這個位置就會增加相應的頻率,張三訪問1萬次,李四訪問1次那byte[1]這個位置就是1萬零1,如果取李四的訪問評率的時候就會取出是1萬零1,但是李四命名只訪問了1次啊,為了解決這個問題,所以用了多個hash算法可以理解為long[][]二維數組的一個概念,比如在第一個算法張三和李四沖突了,但是在第二個,第三個中很大的概率不沖突,比如一個算法大概有1%的概率沖突,那四個算法一起沖突的概率是1%的四次方。通過這個模式我們取李四的訪問率的時候取所有算法中,李四訪問最低頻率的次數。所以他的名字叫Count-Min Sketch。
“推薦一個艿艿寫的 6000+ Star 的 SpringBoot + SpringCloud + Dubbo 教程的倉庫:https://github.com/YunaiV/SpringBoot-Labs
2. 使用
Caffeine Cache 的github地址:點我。
目前的最新版本是:
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.6.2</version> </dependency>2.1 緩存填充策略
Caffeine Cache提供了三種緩存填充策略:手動、同步加載和異步加載。
1.手動加載
在每次get key的時候指定一個同步的函數,如果key不存在就調用這個函數生成一個值。
/***?手動加載*?@param?key*?@return*/ public?Object?manulOperator(String?key)?{Cache<String,?Object>?cache?=?Caffeine.newBuilder().expireAfterWrite(1,?TimeUnit.SECONDS).expireAfterAccess(1,?TimeUnit.SECONDS).maximumSize(10).build();//如果一個key不存在,那么會進入指定的函數生成valueObject?value?=?cache.get(key,?t?->?setValue(key).apply(key));cache.put("hello",value);//判斷是否存在如果不存返回nullObject?ifPresent?=?cache.getIfPresent(key);//移除一個keycache.invalidate(key);return?value; }public?Function<String,?Object>?setValue(String?key){return?t?->?key?+?"value"; }2. 同步加載
構造Cache時候,build方法傳入一個CacheLoader實現類。實現load方法,通過key加載value。
/***?同步加載*?@param?key*?@return*/ public?Object?syncOperator(String?key){LoadingCache<String,?Object>?cache?=?Caffeine.newBuilder().maximumSize(100).expireAfterWrite(1,?TimeUnit.MINUTES).build(k?->?setValue(key).apply(key));return?cache.get(key); }public?Function<String,?Object>?setValue(String?key){return?t?->?key?+?"value"; }3. 異步加載
AsyncLoadingCache是繼承自LoadingCache類的,異步加載使用Executor去調用方法并返回一個CompletableFuture。異步加載緩存使用了響應式編程模型。
如果要以同步方式調用時,應提供CacheLoader。要以異步表示時,應該提供一個AsyncCacheLoader,并返回一個CompletableFuture。
?/***?異步加載**?@param?key*?@return*/ public?Object?asyncOperator(String?key){AsyncLoadingCache<String,?Object>?cache?=?Caffeine.newBuilder().maximumSize(100).expireAfterWrite(1,?TimeUnit.MINUTES).buildAsync(k?->?setAsyncValue(key).get());return?cache.get(key); }public?CompletableFuture<Object>?setAsyncValue(String?key){return?CompletableFuture.supplyAsync(()?->?{return?key?+?"value";}); }2.2 回收策略
Caffeine提供了3種回收策略:基于大小回收,基于時間回收,基于引用回收。
1. 基于大小的過期方式
基于大小的回收策略有兩種方式:一種是基于緩存大小,一種是基于權重。
//?根據緩存的計數進行驅逐 LoadingCache<String,?Object>?cache?=?Caffeine.newBuilder().maximumSize(10000).build(key?->?function(key));//?根據緩存的權重來進行驅逐(權重只是用于確定緩存大小,不會用于決定該緩存是否被驅逐) LoadingCache<String,?Object>?cache1?=?Caffeine.newBuilder().maximumWeight(10000).weigher(key?->?function1(key)).build(key?->?function(key));maximumWeight與maximumSize不可以同時使用。
2.基于時間的過期方式
//?基于固定的到期策略進行退出 LoadingCache<String,?Object>?cache?=?Caffeine.newBuilder().expireAfterAccess(5,?TimeUnit.MINUTES).build(key?->?function(key)); LoadingCache<String,?Object>?cache1?=?Caffeine.newBuilder().expireAfterWrite(10,?TimeUnit.MINUTES).build(key?->?function(key));//?基于不同的到期策略進行退出 LoadingCache<String,?Object>?cache2?=?Caffeine.newBuilder().expireAfter(new?Expiry<String,?Object>()?{@Overridepublic?long?expireAfterCreate(String?key,?Object?value,?long?currentTime)?{return?TimeUnit.SECONDS.toNanos(seconds);}@Overridepublic?long?expireAfterUpdate(@Nonnull?String?s,?@Nonnull?Object?o,?long?l,?long?l1)?{return?0;}@Overridepublic?long?expireAfterRead(@Nonnull?String?s,?@Nonnull?Object?o,?long?l,?long?l1)?{return?0;}}).build(key?->?function(key));Caffeine提供了三種定時驅逐策略:
expireAfterAccess(long, TimeUnit):在最后一次訪問或者寫入后開始計時,在指定的時間后過期。假如一直有請求訪問該key,那么這個緩存將一直不會過期。expireAfterWrite(long, TimeUnit): 在最后一次寫入緩存后開始計時,在指定的時間后過期。expireAfter(Expiry): 自定義策略,過期時間由Expiry實現獨自計算。緩存的刪除策略使用的是惰性刪除和定時刪除。這兩個刪除策略的時間復雜度都是O(1)。
3. 基于引用的過期方式
Java中四種引用類型
| 強引用 Strong Reference | 從來不會 | 對象的一般狀態 | JVM停止運行時終止 |
| 軟引用 Soft Reference | 在內存不足時 | 對象緩存 | 內存不足時終止 |
| 弱引用 Weak Reference | 在垃圾回收時 | 對象緩存 | gc運行后終止 |
| 虛引用 Phantom Reference | 從來不會 | 可以用虛引用來跟蹤對象被垃圾回收器回收的活動,當一個虛引用關聯的對象被垃圾收集器回收之前會收到一條系統通知 | JVM停止運行時終止 |
注意:AsyncLoadingCache不支持弱引用和軟引用。
Caffeine.weakKeys():使用弱引用存儲key。如果沒有其他地方對該key有強引用,那么該緩存就會被垃圾回收器回收。由于垃圾回收器只依賴于身份(identity)相等,因此這會導致整個緩存使用身份 (==) 相等來比較 key,而不是使用 equals()。
Caffeine.weakValues() :使用弱引用存儲value。如果沒有其他地方對該value有強引用,那么該緩存就會被垃圾回收器回收。由于垃圾回收器只依賴于身份(identity)相等,因此這會導致整個緩存使用身份 (==) 相等來比較 key,而不是使用 equals()。
Caffeine.softValues() :使用軟引用存儲value。當內存滿了過后,軟引用的對象以將使用最近最少使用(least-recently-used ) 的方式進行垃圾回收。由于使用軟引用是需要等到內存滿了才進行回收,所以我們通常建議給緩存配置一個使用內存的最大值。softValues() 將使用身份相等(identity) (==) 而不是equals() 來比較值。
Caffeine.weakValues()和Caffeine.softValues()不可以一起使用。
3. 移除事件監聽
Cache<String,?Object>?cache?=?Caffeine.newBuilder().removalListener((String?key,?Object?value,?RemovalCause?cause)?->System.out.printf("Key?%s?was?removed?(%s)%n",?key,?cause)).build();4. 寫入外部存儲
CacheWriter 方法可以將緩存中所有的數據寫入到第三方。
LoadingCache<String,?Object>?cache2?=?Caffeine.newBuilder().writer(new?CacheWriter<String,?Object>()?{@Override?public?void?write(String?key,?Object?value)?{//?寫入到外部存儲}@Override?public?void?delete(String?key,?Object?value,?RemovalCause?cause)?{//?刪除外部存儲}}).build(key?->?function(key));如果你有多級緩存的情況下,這個方法還是很實用。
注意:CacheWriter不能與弱鍵或AsyncLoadingCache一起使用。
5. 統計
與Guava Cache的統計一樣。
Cache<String,?Object>?cache?=?Caffeine.newBuilder().maximumSize(10_000).recordStats().build();通過使用Caffeine.recordStats(), 可以轉化成一個統計的集合. 通過 Cache.stats() 返回一個CacheStats。CacheStats提供以下統計方法:
hitRate():?返回緩存命中率evictionCount():?緩存回收數量averageLoadPenalty():?加載新值的平均時間 “推薦一個艿艿寫的 3000+ Star 的 SpringCloud Alibaba 電商開源項目的倉庫:https://github.com/YunaiV/onemall
3. SpringBoot 中默認Cache-Caffine Cache
SpringBoot 1.x版本中的默認本地cache是Guava Cache。在2.x(Spring Boot 2.0(spring 5)?)版本中已經用Caffine Cache取代了Guava Cache。畢竟有了更優的緩存淘汰策略。
下面我們來說在SpringBoot2.x版本中如何使用cache。
1. 引入依賴:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.6.2</version> </dependency>2. 添加注解開啟緩存支持
添加@EnableCaching注解:
@SpringBootApplication @EnableCaching public?class?SingleDatabaseApplication?{public?static?void?main(String[]?args)?{SpringApplication.run(SingleDatabaseApplication.class,?args);} }3. 配置文件的方式注入相關參數
properties文件
spring.cache.cache-names=cache1 spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=10s或Yaml文件
spring:cache:type:?caffeinecache-names:-?userCachecaffeine:spec:?maximumSize=1024,refreshAfterWrite=60s如果使用refreshAfterWrite配置,必須指定一個CacheLoader.不用該配置則無需這個bean,如上所述,該CacheLoader將關聯被該緩存管理器管理的所有緩存,所以必須定義為CacheLoader<Object, Object>,自動配置將忽略所有泛型類型。
import?com.github.benmanes.caffeine.cache.CacheLoader; import?org.springframework.context.annotation.Bean; import?org.springframework.context.annotation.Configuration;/***?@author:?rickiyang*?@date:?2019/6/15*?@description:*/ @Configuration public?class?CacheConfig?{/***?相當于在構建LoadingCache對象的時候?build()方法中指定過期之后的加載策略方法*?必須要指定這個Bean,refreshAfterWrite=60s屬性才生效*?@return*/@Beanpublic?CacheLoader<String,?Object>?cacheLoader()?{CacheLoader<String,?Object>?cacheLoader?=?new?CacheLoader<String,?Object>()?{@Overridepublic?Object?load(String?key)?throws?Exception?{return?null;}//?重寫這個方法將oldValue值返回回去,進而刷新緩存@Overridepublic?Object?reload(String?key,?Object?oldValue)?throws?Exception?{return?oldValue;}};return?cacheLoader;} }Caffeine常用配置說明:
initialCapacity=[integer]:?初始的緩存空間大小maximumSize=[long]:?緩存的最大條數maximumWeight=[long]:?緩存的最大權重expireAfterAccess=[duration]:?最后一次寫入或訪問后經過固定時間過期expireAfterWrite=[duration]:?最后一次寫入后經過固定時間過期refreshAfterWrite=[duration]:?創建緩存或者最近一次更新緩存后經過固定的時間間隔,刷新緩存weakKeys:?打開key的弱引用weakValues:打開value的弱引用softValues:打開value的軟引用recordStats:開發統計功能注意:expireAfterWrite和expireAfterAccess同時存在時,以expireAfterWrite為準。maximumSize和maximumWeight不可以同時使用weakValues和softValues不可以同時使用需要說明的是,使用配置文件的方式來進行緩存項配置,一般情況能滿足使用需求,但是靈活性不是很高,如果我們有很多緩存項的情況下寫起來會導致配置文件很長。所以一般情況下你也可以選擇使用bean的方式來初始化Cache實例。
下面的演示使用bean的方式來注入:
package?com.rickiyang.learn.cache;import?com.github.benmanes.caffeine.cache.CacheLoader; import?com.github.benmanes.caffeine.cache.Caffeine; import?org.apache.commons.compress.utils.Lists; import?org.springframework.cache.CacheManager; import?org.springframework.cache.caffeine.CaffeineCache; import?org.springframework.cache.support.SimpleCacheManager; import?org.springframework.context.annotation.Bean; import?org.springframework.context.annotation.Configuration; import?org.springframework.context.annotation.Primary;import?java.util.ArrayList; import?java.util.List; import?java.util.concurrent.TimeUnit;/***?@author:?rickiyang*?@date:?2019/6/15*?@description:*/ @Configuration public?class?CacheConfig?{/***?創建基于Caffeine的Cache?Manager*?初始化一些key存入*?@return*/@Bean@Primarypublic?CacheManager?caffeineCacheManager()?{SimpleCacheManager?cacheManager?=?new?SimpleCacheManager();ArrayList<CaffeineCache>?caches?=?Lists.newArrayList();List<CacheBean>?list?=?setCacheBean();for(CacheBean?cacheBean?:?list){caches.add(new?CaffeineCache(cacheBean.getKey(),Caffeine.newBuilder().recordStats().expireAfterWrite(cacheBean.getTtl(),?TimeUnit.SECONDS).maximumSize(cacheBean.getMaximumSize()).build()));}cacheManager.setCaches(caches);return?cacheManager;}/***?初始化一些緩存的?key*?@return*/private?List<CacheBean>?setCacheBean(){List<CacheBean>?list?=?Lists.newArrayList();CacheBean?userCache?=?new?CacheBean();userCache.setKey("userCache");userCache.setTtl(60);userCache.setMaximumSize(10000);CacheBean?deptCache?=?new?CacheBean();deptCache.setKey("userCache");deptCache.setTtl(60);deptCache.setMaximumSize(10000);list.add(userCache);list.add(deptCache);return?list;}class?CacheBean?{private?String?key;private?long?ttl;private?long?maximumSize;public?String?getKey()?{return?key;}public?void?setKey(String?key)?{this.key?=?key;}public?long?getTtl()?{return?ttl;}public?void?setTtl(long?ttl)?{this.ttl?=?ttl;}public?long?getMaximumSize()?{return?maximumSize;}public?void?setMaximumSize(long?maximumSize)?{this.maximumSize?=?maximumSize;}}}創建了一個SimpleCacheManager作為Cache的管理對象,然后初始化了兩個Cache對象,分別存儲user,dept類型的緩存。當然構建Cache的參數設置我寫的比較簡單,你在使用的時候酌情根據需要配置參數。
4. 使用注解來對 cache 增刪改查
我們可以使用spring提供的?@Cacheable、@CachePut、@CacheEvict等注解來方便的使用caffeine緩存。
如果使用了多個cahce,比如redis、caffeine等,必須指定某一個CacheManage為@primary,在@Cacheable注解中沒指定 cacheManager 則使用標記為primary的那個。
cache方面的注解主要有以下5個:
-
@Cacheable 觸發緩存入口(這里一般放在創建和獲取的方法上,@Cacheable注解會先查詢是否已經有緩存,有會使用緩存,沒有則會執行方法并緩存)
-
@CacheEvict 觸發緩存的eviction(用于刪除的方法上)
-
@CachePut 更新緩存且不影響方法執行(用于修改的方法上,該注解下的方法始終會被執行)
-
@Caching 將多個緩存組合在一個方法上(該注解可以允許一個方法同時設置多個注解)
-
@CacheConfig 在類級別設置一些緩存相關的共同配置(與其它緩存配合使用)
說一下@Cacheable?和?@CachePut的區別:
@Cacheable:它的注解的方法是否被執行取決于Cacheable中的條件,方法很多時候都可能不被執行。
@CachePut:這個注解不會影響方法的執行,也就是說無論它配置的條件是什么,方法都會被執行,更多的時候是被用到修改上。
簡要說一下Cacheable類中各個方法的使用:
public?@interface?Cacheable?{/***?要使用的cache的名字*/@AliasFor("cacheNames")String[]?value()?default?{};/***?同value(),決定要使用那個/些緩存*/@AliasFor("value")String[]?cacheNames()?default?{};/***?使用SpEL表達式來設定緩存的key,如果不設置默認方法上所有參數都會作為key的一部分*/String?key()?default?"";/***?用來生成key,與key()不可以共用*/String?keyGenerator()?default?"";/***?設定要使用的cacheManager,必須先設置好cacheManager的bean,這是使用該bean的名字*/String?cacheManager()?default?"";/***?使用cacheResolver來設定使用的緩存,用法同cacheManager,但是與cacheManager不可以同時使用*/String?cacheResolver()?default?"";/***?使用SpEL表達式設定出發緩存的條件,在方法執行前生效*/String?condition()?default?"";/***?使用SpEL設置出發緩存的條件,這里是方法執行完生效,所以條件中可以有方法執行后的value*/String?unless()?default?"";/***?用于同步的,在緩存失效(過期不存在等各種原因)的時候,如果多個線程同時訪問被標注的方法*?則只允許一個線程通過去執行方法*/boolean?sync()?default?false;}基于注解的使用方法:
package?com.rickiyang.learn.cache;import?com.rickiyang.learn.entity.User; import?org.springframework.cache.annotation.CacheEvict; import?org.springframework.cache.annotation.CachePut; import?org.springframework.cache.annotation.Cacheable; import?org.springframework.stereotype.Service;/***?@author:?rickiyang*?@date:?2019/6/15*?@description:?本地cache*/ @Service public?class?UserCacheService?{/***?查找*?先查緩存,如果查不到,會查數據庫并存入緩存*?@param?id*/@Cacheable(value?=?"userCache",?key?=?"#id",?sync?=?true)public?void?getUser(long?id){//查找數據庫}/***?更新/保存*?@param?user*/@CachePut(value?=?"userCache",?key?=?"#user.id")public?void?saveUser(User?user){//todo?保存數據庫}/***?刪除*?@param?user*/@CacheEvict(value?=?"userCache",key?=?"#user.id")public?void?delUser(User?user){//todo?保存數據庫} }如果你不想使用注解的方式去操作緩存,也可以直接使用SimpleCacheManager獲取緩存的key進而進行操作。
注意到上面的key使用了spEL 表達式。Spring Cache提供了一些供我們使用的SpEL上下文數據,下表直接摘自Spring官方文檔:
| methodName | root對象 | 當前被調用的方法名 | #root.methodname |
| method | root對象 | 當前被調用的方法 | #root.method.name |
| target | root對象 | 當前被調用的目標對象實例 | #root.target |
| targetClass | root對象 | 當前被調用的目標對象的類 | #root.targetClass |
| args | root對象 | 當前被調用的方法的參數列表 | #root.args[0] |
| caches | root對象 | 當前方法調用使用的緩存列表 | #root.caches[0].name |
| Argument Name | 執行上下文 | 當前被調用的方法的參數,如findArtisan(Artisan artisan),可以通過#artsian.id獲得參數 | #artsian.id |
| result | 執行上下文 | 方法執行后的返回值(僅當方法執行后的判斷有效,如 unless cacheEvict的beforeInvocation=false) | #result |
注意:
1.當我們要使用root對象的屬性作為key時我們也可以將“#root”省略,因為Spring默認使用的就是root對象的屬性。如
@Cacheable(key?=?"targetClass?+?methodName?+#p0")2.使用方法參數時我們可以直接使用“#參數名”或者“#p參數index”。如:
@Cacheable(value="userCache",?key="#id") @Cacheable(value="userCache",?key="#p0")SpEL提供了多種運算符
| 關系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
| 算術 | +,- ,* ,/,%,^ |
| 邏輯 | &&,||,!,and,or,not,between,instanceof |
| 條件 | ?: (ternary),?: (elvis) |
| 正則表達式 | matches |
| 其他類型 | ?.,?[…],![…],^[…],$[…] |
總結
以上是生活随笔為你收集整理的Caffeine Cache~高性能 Java 本地缓存之王的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis 10亿数据量只需要100MB
- 下一篇: Synchronized 天天用,实现原