一、前言
??最近在和同事一塊做一個(gè)需求的時(shí)候,看了下同事的代碼湘纵,在使用緩存這塊看到了Spring Cache這個(gè)玩意塑崖,感到很陌生。看了下簡介,這玩意從Spring 3.X之后都有了,但我卻一點(diǎn)也不了解妥箕,感到很慚愧,于是趕緊花了點(diǎn)時(shí)間學(xué)習(xí)下更舞。
本文已學(xué)習(xí)注解為主畦幢,畢竟Spring Boot這類注解式的框架已逐漸成為了主流。參考的Spring版本是4.3.15缆蝉。
二宇葱、Spring Cache注解
1. 簡介
Spring Cache是Spring3.1之后引入的基于注解的緩存方案,旨在通過少量的注解刊头,達(dá)到能完成緩存功能的效果黍瞧。其中涉及到的注解如下:
- EnableCaching
- Cacheable
- CacheEvict
- CachePut
- CacheConfig
- Caching
??目前版本包含了這六個(gè)注解,接下來我們將挨個(gè)簡單介紹下這些注解原杂,然后提供一些簡單的例子來作為參考:
- 首先印颤,這些注解都位于
spring-context
包下,具體地址是org.springframework.cache.annotation
包下穿肄,我們通過API學(xué)習(xí)的時(shí)候可以根據(jù)這個(gè)地址進(jìn)行查找年局。- 其次,Spring Cache的XML實(shí)現(xiàn)被碗,是通過命名空間cache來實(shí)現(xiàn)的某宪,如果要基于XML來使用的話仿村,記得先引入cache命名空間锐朴,接下來我們介紹各個(gè)注解的時(shí)候,會順便提一下對應(yīng)的XML的配置蔼囊。
2. EnableCaching注解
??該注解是最基礎(chǔ)的注解焚志,用于啟用Spring的緩存管理功能,類似于Spring XML配置中的<cache:annotation-driven> 標(biāo)簽畏鼓。開啟Spring的緩存功能后酱酬,我們還需要配置CacheManager對象。
??CacheManager是用于管理spring cache的實(shí)現(xiàn)的云矫,只要使用spring cache膳沽,該對象是必須要配置的。該接口有多種實(shí)現(xiàn)方式,比如 CaffeineCacheManager挑社,EhCacheCacheManager陨界,ConcurrentMapCacheManager,SimpleCacheManager等痛阻;我們來看一個(gè)簡單的配置:
@Configuration
@EnableCaching
public class AppConfig {
@Bean
public CacheManager cacheManager() {
// configure and return an implementation of Spring's CacheManager SPI
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
}
}
不過我們也可以繼承CachingConfigurerSupport來配置:
@Configuration
@EnableCaching
public class AppConfig extends CachingConfigurerSupport {
@Bean
@Override
public CacheManager cacheManager() {
// configure and return an implementation of Spring's CacheManager SPI
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
}
@Bean
@Override
public KeyGenerator keyGenerator() {
// configure and return an implementation of Spring's KeyGenerator SPI
return new MyKeyGenerator();
}
}
而EnableCaching注解菌瘪,有以下三個(gè)參數(shù):
- proxyTargetClass,是否使用CGLIB代理阱当,而不是默認(rèn)的Java接口代理俏扩。默認(rèn)是false;
- mode弊添,緩存的代理模式录淡,默認(rèn)是AdviceMode.PROXY,有兩種選擇表箭,另一種是AdviceMode.ASPECTJ赁咙。使用proxy代理的話,有一個(gè)問題就是免钻,緩存方法在外部調(diào)用的時(shí)候才會被攔截生效彼水,而同一個(gè)類的本地調(diào)用不會被攔截;這個(gè)問題適用于所有使用spring代理的情況极舔,比如Transactional, Async等相關(guān)注解凤覆;并且proxy模式下,只有public方法上的才會生效拆魏;
- order盯桦,緩存的順序;
因?yàn)镾pring Cache是基于Spring的代理模式來實(shí)現(xiàn)的渤刃,所以上面存在的內(nèi)部調(diào)用問題還是要注意下拥峦。
3. Cacheable注解
該注解用于創(chuàng)建緩存,同樣來看一下它的參數(shù)卖子。
- value/cacheNames略号,這兩個(gè)參數(shù)用于表示緩存的前綴,數(shù)組類型洋闽,可以指定多個(gè)玄柠;
- condition,SpEL 表達(dá)式诫舅,用于條件過濾羽利,表示當(dāng)滿足該條件的情況下才進(jìn)行緩存;true或者false刊懈,只有為true時(shí)才進(jìn)行緩存这弧;
- key娃闲,SpEL表達(dá)式,緩存的key匾浪,Spring提供了專用的緩存相關(guān)元數(shù)據(jù)root對象和result:
(redisKey = cacheNames::key (注意中間的兩個(gè)冒號))
元數(shù)據(jù) | 對應(yīng)的描述 | 示例 |
---|---|---|
methodName | 當(dāng)前方法名 | #root.methodName |
method | 當(dāng)前方法 | #root.method.name |
target | 當(dāng)前被調(diào)用的對象 | #root.target |
targetClass | 當(dāng)前被調(diào)用的對象的class | #root.targetClass |
args | 當(dāng)前方法參數(shù)組成的數(shù)組 | #root.args[0] |
caches | 當(dāng)前被調(diào)用的方法使用的Cache | #root.caches[0].name |
參數(shù)名 | 方法參數(shù)的名字畜吊,可以直接 #參數(shù)名或者使用 #p0或#a0 的 形式, 0代表參數(shù)的索引户矢; |
#iban , #a0 , #p0 , #p<#arg>
|
result | 方法執(zhí)行完成的返回值(僅當(dāng)方法執(zhí)行之后的判斷有效玲献,如 unless , beforeInvocation=false ) |
#result |
當(dāng)然,我們在使用root對象的屬性作為key時(shí)我們也可以將“#root”省略梯浪,因?yàn)镾pring默認(rèn)使用的就是root對象的屬性捌年。
其中key的生成策略有兩種,一種是默認(rèn)策略挂洛,一種是自定義策略礼预,但無論哪種策略,都是借助KeyGenerator生成的虏劲,其默認(rèn)策略如下:
- 1.如果方法沒有參數(shù)托酸,則key是
SimpleKey.EMPTY
; - 2.如果只有一個(gè)參數(shù)的話則使用該參數(shù)作為key;
- 3.如果參數(shù)多于一個(gè)的話,則返回一個(gè)包含所有參數(shù)的
SimpleKey
柒巫;
The default key generation strategy changed with the release of Spring 4.0. Earlier versions of Spring used a key generation strategy that, for multiple key parameters, only considered the hashCode() of parameters and not equals(); this could cause unexpected key collisions (see SPR-10237 for background). The new 'SimpleKeyGenerator' uses a compound key for such scenarios.
大概意思是励堡,原先版本默認(rèn)的生成策略,對于多個(gè)參數(shù)的情況只考慮了參數(shù)的hashcode堡掏,而沒有考慮equals应结,這有可能導(dǎo)致意外的鍵沖突,而新的SimpleKeyGenerator
則使用了符合鍵的操作泉唁。
如果默認(rèn)策略滿足不了的話鹅龄,我們可以自定義我們的KeyGenerator,然后指定Spring Cache使用的KeyGenerator為我們自己定義的KeyGenerator即可亭畜;
@Cacheable(value = "userCache", key = "#a0")
public User test(String id) {
System.out.println("-------------------測試緩存方法-------------");
User user = queryDb(id);
return user;
}
- keyGenerator扮休,前面已經(jīng)說過,key的自定義生成器拴鸵,如果不想使用key屬性玷坠,可以自定義key的實(shí)現(xiàn),繼承KeyGenerator宝踪;該屬性和key是互斥的侨糟;
- cacheManager碍扔,cacheManager的bean配置瘩燥;
- cacheResolver,同樣不同,如果不想使用默認(rèn)的cacheManager的幾種實(shí)現(xiàn)厉膀,也可以通過該屬性來自定義實(shí)現(xiàn)溶耘,該屬性與cacheManager互斥;
- unless服鹅,用于否決緩存的凳兵,和condition不同,condition是在方法開始之前判斷企软,而該屬性是在方法執(zhí)行完成之后判斷庐扫;所以condition不能通過方法返回值來判斷,而unless屬性可以仗哨;方法返回值是通過元數(shù)據(jù)result來表示的形庭,true時(shí)表示不會緩存,為false時(shí)表示進(jìn)行緩存厌漂;
@Cacheable(value = "userCache", unless = "#result == null")
public User test(String id) {
System.out.println("-------------------測試緩存方法-------------");
User user = queryDb(id);
return user;
}
需要簡單注意的是萨醒,返回值如果是Optional的話,表示的是實(shí)際的對象苇倡,而不是包裝器對象富纸;同樣,key的幾種表示方式也適合該屬性旨椒;
- sync晓褪,表示是否異步,默認(rèn)為false综慎,也就是同步辞州;一般情況下,Spring的Cache的緩存過期之后寥粹,這時(shí)候如果多個(gè)線程同時(shí)對某個(gè)數(shù)據(jù)進(jìn)行訪問变过,會同時(shí)去訪問數(shù)據(jù)庫,有可能導(dǎo)致數(shù)據(jù)庫的壓力頓時(shí)增大涝涤,所以Spring4.3之后引入了sync注解媚狰,當(dāng)設(shè)置它為true時(shí),會將緩存鎖定阔拳,只有一個(gè)線程的請求會去訪問數(shù)據(jù)庫崭孤,其他線程都會等待直到緩存可用,這個(gè)設(shè)置可以減少對數(shù)據(jù)庫的瞬間并發(fā)訪問糊肠;
- 注意辨宠,不支持unless;實(shí)際上該屬性只是一個(gè)提示或建議货裹,至于是否支持要看我們的cache provider 嗤形;
4. CacheEvict注解
??該注解用于緩存的清除,由于該注解的參數(shù)與Cacheable的參數(shù)大部分都是相同的弧圆,這里來簡單介紹下CacheEvict注解獨(dú)有的兩個(gè)參數(shù):
- allEntries赋兵,是否清空所有的緩存內(nèi)容笔咽,默認(rèn)為false,如果指定為 true霹期,則方法調(diào)用后將清空所有緩存叶组;不過不允許在該值設(shè)置為true的情況下,再設(shè)置key的值历造;
- beforeInvocation甩十,是否在調(diào)用該方法之前清空緩存,默認(rèn)為false吭产;如果為true枣氧,在該方法被調(diào)用前就清空緩存,不用考慮該方法的執(zhí)行結(jié)果(即不考慮是否拋出異常)垮刹;而默認(rèn)情況下达吞,如果方法執(zhí)行時(shí)發(fā)生異常,則不會清除緩存荒典;
@CacheEvict(value = "userCache", key = "#a0", allEntries = true)
public User evict(String id) {
return queryDb(id);
}
5. CachePut注解
該注解用于緩存的更新酪劫,不過需要注意的是,如果返回值是JDK 8的Optional的類型的話程序會自動處理寺董;參數(shù)方面和Cacheable完全一致覆糟,就不多說了。
6. CacheConfig注解
??用于類級別的緩存的公用配置遮咖。有的時(shí)候滩字,一個(gè)類中多個(gè)緩存可能會有重復(fù)的屬性,我們可以使用該注解將這些重復(fù)的屬性提取出來御吞。參數(shù)有cacheNames麦箍、keyGenerator、cacheManager陶珠、cacheResolver這幾個(gè)屬性挟裂,我們前文已經(jīng)說過,這里不多說了揍诽;
7. Caching注解
用于組合多個(gè)緩存的配置诀蓉,是一個(gè)組注解;屬性直接看源碼就知道了:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}};
使用的話可以看下:
@Caching(cacheable = {@Cacheable(value = "userCache", key = "#a0", unless = "#"),
@Cacheable(value = "userCache", key = "#a0", unless = "#")})
8. 總結(jié)
??Spring Cache中涉及到的常用的注解就這幾個(gè)暑脆,其中多個(gè)注解的參數(shù)還都是相同的渠啤,所以學(xué)習(xí)起來比較簡單。
- 雖然這些注解可以用于方法和類上添吗,但一般情況下都是用于方法上沥曹;
- 這些中的大部分注解都可以作為元注解,所以我們可以在這些注解基礎(chǔ)上自定義我們的注解根资;
三架专、Spring Cache的XML配置
雖然本篇文章的重點(diǎn)是介紹注解的,但還是簡單來說下XML中cache的配置玄帕。Spring的Cache的XML配置部脚,最外層的標(biāo)簽只有兩個(gè):
1. <cache:annotation-driven>
該標(biāo)簽我們前文簡單說過,用于開啟Spring的緩存功能裤纹,其中該標(biāo)簽的幾個(gè)屬性和EnableCaching注解的幾個(gè)參數(shù)是相同的:
- mode委刘,proxy-target-class,order鹰椒,這三個(gè)參數(shù)EnableCaching注解是相同的锡移;
- cache-manager,這個(gè)我們也說過了漆际,用于配置緩存的實(shí)現(xiàn)CacheManager淆珊,key-generator,這個(gè)則是自定義key的實(shí)現(xiàn)奸汇;
<cache:annotation-driven key-generator="" cache-manager="" order="" proxy-target-class=""
mode="proxy"/>
2. <cache:advice>
該標(biāo)簽則是用于配置緩存的具體實(shí)現(xiàn)施符,包含創(chuàng)建,清除擂找,更新等對應(yīng)功能戳吝。我們先來看下配置:
<cache:advice cache-manager="" key-generator="" id="">
<cache:caching key-generator="" cache-manager="" condition="" cache="" key="" method="">
<cache:cacheable method="" key="" cache="" condition="" cache-manager="" key-generator="" unless=""/>
<cache:cache-put method="" key="" cache="" condition="" cache-manager="" key-generator="" unless=""/>
<cache:cache-evict method="" key="" cache="" condition="" cache-manager="" key-generator="" before-invocation="true" all-entries="true"/>
</cache:caching>
</cache:advice>
<cache:advice>的所有配置都在這了,而這些配置屬性與注解中的屬性是一一對應(yīng)的贯涎,這里就不多說了听哭。
四、Spring Cache 的一些小問題
1. 緩存有效時(shí)間問題
??前面學(xué)習(xí)@Cacheable的使用的時(shí)候塘雳,沒有涉及到有關(guān)緩存的有效時(shí)間的設(shè)置陆盘。這是因?yàn)镾pring Cache自帶的默認(rèn)的基于ConcurrentHashMap
的CacheManager實(shí)現(xiàn)是沒有自動過期這一功能的。Spring Cache支持了許多第三方的Cache實(shí)現(xiàn)败明,不同的Cache對過期時(shí)間的處理是不一樣的礁遣,所以我們?nèi)绻枰獙?shí)現(xiàn)有效時(shí)間的問題,可以采用如下幾種方式:
- 以@Cachable為元注解肩刃,自定義我們的的注解實(shí)現(xiàn)祟霍;
- 在@CacheEvict基礎(chǔ)上,結(jié)合Scheduled注解盈包,通過定時(shí)任務(wù)的形式來實(shí)現(xiàn)沸呐,不過要注意多個(gè)key的問題;
- 使用第三方Cache呢燥,如Redis崭添,EhCache等實(shí)現(xiàn);
比如說:
public CacheManager cacheManager() {
RedisCacheManager redisCacheManager =newRedisCacheManager(redisTemplate());
redisCacheManager.setTransactionAware(true);
redisCacheManager.setLoadRemoteCachesOnStartup(true);
redisCacheManager.setUsePrefix(true);
//配置緩存的過期時(shí)間
Mapexpires =newHashMap();
expires.put("token",expiration);
redisCacheManager.setExpires(expires);
return redisCacheManager;
}
這里截取Spring Cache文檔最后一節(jié):
36.8 How can I set the TTL/TTI/Eviction policy/XXX feature?
Directly through your cache provider. The cache abstraction is… well, an abstraction not a cache implementation. The solution you are using might support various data policies and different topologies which other solutions do not (take for example the JDK ConcurrentHashMap) - exposing that in the cache abstraction would be useless simply because there would no backing support. Such functionality should be controlled directly through the backing cache, when configuring it or through its native API.
本文參考自:
Spring 4.3.15 官方API 文檔叛氨,Spring 4.3.15 官方文檔(強(qiáng)烈建議查看)
IBM社區(qū)-注釋驅(qū)動的 Spring cache 緩存介紹
使用Spring4.3解決緩存過期后多線程并發(fā)訪問數(shù)據(jù)庫的問題