Spring基于注解的緩存聲明深入探究
一、概述
從3.1版本起,Spring框架就已經(jīng)支持將緩存添加到現(xiàn)有的Spring應(yīng)用中,和事務(wù)支持一樣,緩存抽象允許在對(duì)代碼影響最小的情況下一致性地使用各種緩存解決方案。
從Spring 4.1版本起,有了JSR-107注解和更多定制化的選項(xiàng)支持后,緩存抽象有了重大的改進(jìn)。
二、聲明式基于注解的緩存
對(duì)于緩存聲明,該抽象提供了一套Java注解:
@Cacheable:觸發(fā)緩存構(gòu)建。@CacheEvict:觸發(fā)緩存銷毀。@CachePut:更新緩存。@Caching:重組應(yīng)用到方法上的多個(gè)緩存操作。@CacheConfig:類級(jí)別共享緩存相關(guān)的通用設(shè)置。
1、@Cacheable注解
正如其名,@Cacheable注解用來(lái)區(qū)分方法執(zhí)行結(jié)果是否應(yīng)該被緩存,如果后續(xù)該方法再次被調(diào)用,方法的執(zhí)行結(jié)果直接從緩存中獲取,而不會(huì)調(diào)用實(shí)際的方法邏輯。示例如下:
@Cacheable("books")
public Book findBook(ISBN isbn) {...}當(dāng)然我們也可以指定多個(gè)緩存名稱,如果至少一個(gè)緩存被命中,那么關(guān)聯(lián)的緩存結(jié)果就會(huì)返回,示例如下:
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
(1) 默認(rèn)緩存key的生成
因?yàn)榫彺娑际莐ey-value存儲(chǔ),每次緩存方法的調(diào)用都會(huì)被轉(zhuǎn)義為緩存key的訪問(wèn)。Spring緩存抽象對(duì)于key的生成會(huì)采用KeyGenerator來(lái)生成,算法如下:
- 如果沒(méi)有方法參數(shù),返回
SimpleKey.EMPTY。 - 如果該方法只有一個(gè)參數(shù),返回參數(shù)實(shí)例。
- 如果方法不止一個(gè)參數(shù),返回包含所有參數(shù)的
SimpleKey實(shí)例。
這種key的生成策略適用于大部分場(chǎng)景,只要方法參數(shù)合理實(shí)現(xiàn)了hashCode()和equals()方法。
SimpleKeyGenerator源碼如下:
public class SimpleKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}備注:如果要實(shí)現(xiàn)自定義key生成策略,需要實(shí)現(xiàn)org.springframework.cache.interceptor.KeyGenerator接口。
(2) 聲明式自定義key生成
目標(biāo)方法可能會(huì)有多個(gè)參數(shù),有些參數(shù)可能只應(yīng)用于方法邏輯,而不適合用作key的生成,例如:
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
對(duì)于這種情況,@Cacheable注解有一個(gè)key屬性,通過(guò)該屬性可以自定義Key生成。我們也可以使用SPEL(Spring表達(dá)式語(yǔ)言)去指定參數(shù)或者參數(shù)的嵌套屬性,示例如下:
@Cacheable(cacheNames="books", key="#isbn") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#isbn.rawNumber") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
當(dāng)然,我們也可以通過(guò)@Cacheable注解指定自定義的KeyGenerator實(shí)例,示例如下:
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
備注:key和keyGenerator參數(shù)是互斥的,如果兩者都指定會(huì)觸發(fā)異常。
(3) 默認(rèn)緩存解析
Spring緩存抽象通過(guò)CacheResolver去解析操作級(jí)別的緩存,而CacheResolver會(huì)用CacheManager去獲取緩存,接口定義如下:
@FunctionalInterface
public interface CacheResolver {
/**
* Return the cache(s) to use for the specified invocation.
* @param context the context of the particular invocation
* @return the cache(s) to use (never {@code null})
* @throws IllegalStateException if cache resolution failed
*/
Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context);
}
public interface CacheManager {
/**
* Get the cache associated with the given name.
* <p>Note that the cache may be lazily created at runtime if the
* native provider supports it.
* @param name the cache identifier (must not be {@code null})
* @return the associated cache, or {@code null} if such a cache
* does not exist or could be not created
*/
@Nullable
Cache getCache(String name);
/**
* Get a collection of the cache names known by this manager.
* @return the names of all caches known by the cache manager
*/
Collection<String> getCacheNames();
}(4) 自定義緩存解析
默認(rèn)緩存解析對(duì)于單CacheManager應(yīng)用適應(yīng)很好,對(duì)于有多個(gè)緩存管理器的應(yīng)用,我們可以對(duì)每個(gè)操作設(shè)置緩存管理器,如下:
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")
public Book findBook(ISBN isbn) {...}
(5) 條件式緩存
有時(shí)方法緩存結(jié)果可能要取決于指定的參數(shù),緩存注解通過(guò)支持SPEL的condition屬性實(shí)現(xiàn)該功能,示例如下:
@Cacheable(cacheNames="book", condition="#name.length() < 32") public Book findBook(String name)
備注:只有當(dāng)參數(shù)name的長(zhǎng)度小于32時(shí),方法結(jié)果才會(huì)被緩存。
除了condition屬性,unless屬性可以用來(lái)決定方法返回值不緩存。與condition不同,unless表達(dá)式在方法被調(diào)用后才會(huì)執(zhí)行,示例如下:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") public Book findBook(String name)
備注:如果Book對(duì)象的hardback屬性為true則不緩存,為false才緩存。
當(dāng)然,緩存抽象同時(shí)也支持java.util.Optional,只有當(dāng)Optional中的值存在時(shí),方法返回值才會(huì)被緩存。#result代表方法的執(zhí)行結(jié)果,上面的我們可以改寫:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback") public Optional<Book> findBook(String name)
備注;#result引用的始終是Book對(duì)象,而不是Optional對(duì)象,因?yàn)榉祷刂悼赡転榭?,所以我們?yīng)該使用安全導(dǎo)航操作符 => ?.
關(guān)于其它可以用的緩存SpEL表達(dá)式上下文,可以參考:Available Caching SpEL Evaluation Context。
2、@CachePut注解
這個(gè)注解主要用于更新緩存,也就說(shuō)帶有該注解的方法總是會(huì)執(zhí)行,并且方法的返回值會(huì)刷新緩存。該注解和@Cacheable的參數(shù)相同,示例如下:
@CachePut(cacheNames="book", key="#isbn") public Book updateBook(ISBN isbn, BookDescriptor descriptor)
備注:@CachePut和@Cacheable的主要區(qū)別在于后者會(huì)通過(guò)緩存跳過(guò)方法的執(zhí)行,而前者為了更新緩存會(huì)迫使方法執(zhí)行。
3、@CacheEvict注解
這個(gè)注解主要用來(lái)清除緩存,與@Cacheable注解相反,方法的執(zhí)行會(huì)觸發(fā)從緩存中刪除數(shù)據(jù)。@CacheEvit注解要求指定一個(gè)或多個(gè)緩存名。除此之外,該注解還有一個(gè)額外的屬性allEntries,指定該屬性值為true后會(huì)清除某個(gè)緩存名下的所有緩存key。示例如下:
@CacheEvict(cacheNames="books", allEntries=true) public void loadBooks(InputStream batch)
備注:緩存名為books下的所有緩存key都會(huì)被清除。
4、@Caching注解
有些情況下,相同類型多個(gè)注解,如@CacheEvict或者@CachePut需要被指定。@Caching注解允許多個(gè)嵌套@Cacheable、@CachePut、@CacheEvict注解用在同一個(gè)方法上。示例如下:
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
5、@CacheConfig注解
目前我們已經(jīng)了解到緩存操作提供了很多定制化的選項(xiàng),然而有些定制化選項(xiàng)如果應(yīng)用到類中的所有操作可能會(huì)有些冗余,示例如下:
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}
@CacheConfig是一個(gè)類級(jí)別的注解,這個(gè)注解可以共享緩存名稱,自定義的KeyGenerator,自定義的CacheManager和自定義的CacheResolver。
方法操作級(jí)別的自定義選項(xiàng)總是會(huì)重寫@CacheConfig中的自定義選項(xiàng)。下面是每個(gè)緩存操作自定義選項(xiàng)對(duì)應(yīng)的3個(gè)級(jí)別,優(yōu)先級(jí)從上至下越來(lái)越高。
- 全局配置的
CacheManager,KeyGenerator等。 - 類級(jí)別,通過(guò)
@CacheConfig指定。 - 方法操作級(jí)別。
三、開(kāi)啟聲明式緩存注解
直接在配置類上加上#EnableCaching即可,如下:
@Configuration
@EnableCaching
public class AppConfig {
}四、使用自定義注解
Spring緩存抽象允許我們用自定義注解去標(biāo)識(shí)什么方法可以觸發(fā)緩存構(gòu)建或者消除。@Cacheable, @CachePut, @CacheEvict and @CacheConfig這些注解都可以作為元注解,其實(shí)即使可以修飾其它注解,示例如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface SlowService {
}上面我們自定義了SlowService注解,該注解被@Cacheable所修飾,現(xiàn)在我們可以用自定義注解代替如下代碼:
@Cacheable(cacheNames="books", key="#isbn") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
替代代碼如下:
@SlowService public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
盡管@SlowService注解并不是Spring原生注解,但Spring容器會(huì)在運(yùn)行時(shí)識(shí)別并且知道它是用來(lái)干嘛的。
備注:后面我們會(huì)利用自定義注解實(shí)現(xiàn)自定義過(guò)期時(shí)間的緩存方案。
到此這篇關(guān)于Spring基于注解的緩存聲明深入探究的文章就介紹到這了,更多相關(guān)Spring注解的緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot整合hutool驗(yàn)證碼的實(shí)例代碼
在 Spring Boot 中,你可以將 Hutool 生成驗(yàn)證碼的功能集成到 RESTful API 接口中,這篇文章主要介紹了Springboot整合hutool驗(yàn)證碼,需要的朋友可以參考下2024-08-08
mybatis類型轉(zhuǎn)換器如何實(shí)現(xiàn)數(shù)據(jù)加解密
這篇文章主要介紹了mybatis類型轉(zhuǎn)換器如何實(shí)現(xiàn)數(shù)據(jù)加解密,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
SpringBoot配置Access-Control-Allow-Origin教程
文章介紹了三種配置Spring Boot跨域訪問(wèn)的方法:1. 使用過(guò)濾器;2. 在WebConfig配置文件中設(shè)置;3. 通過(guò)注解配置,作者分享了個(gè)人經(jīng)驗(yàn),并鼓勵(lì)讀者支持腳本之家2025-03-03
java的arraylist排序示例(arraylist用法)
這篇文章主要介紹了java的arraylist排序示例,學(xué)習(xí)一下arraylist的用法,需要的朋友可以參考下2014-03-03
java實(shí)現(xiàn)學(xué)生管理系統(tǒng)(面向?qū)ο?
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生管理系統(tǒng)(面向?qū)ο螅闹惺纠a介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03
詳解RabbitMQ中延遲隊(duì)列結(jié)合業(yè)務(wù)場(chǎng)景的使用
這篇文章主要介紹了詳解RabbitMQ中延遲隊(duì)列結(jié)合業(yè)務(wù)場(chǎng)景的使用,延遲隊(duì)列中的元素都是帶有時(shí)間屬性的,延遲隊(duì)列就是用來(lái)存放需要在指定時(shí)間被處理的元素的隊(duì)列,需要的朋友可以參考下2023-05-05
SpringBoot Maven Clean報(bào)錯(cuò)解決方案
這篇文章主要介紹了SpringBoot Maven Clean報(bào)錯(cuò)解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
Spring?EnableAsync注解異步執(zhí)行源碼解析
這篇文章主要為大家介紹了Spring?EnableAsync注解源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

