Spring基于注解的緩存聲明深入探究
一、概述
從3.1版本起,Spring框架就已經(jīng)支持將緩存添加到現(xiàn)有的Spring應(yīng)用中,和事務(wù)支持一樣,緩存抽象允許在對代碼影響最小的情況下一致性地使用各種緩存解決方案。
從Spring 4.1版本起,有了JSR-107
注解和更多定制化的選項支持后,緩存抽象有了重大的改進。
二、聲明式基于注解的緩存
對于緩存聲明,該抽象提供了一套Java注解:
@Cacheable
:觸發(fā)緩存構(gòu)建。@CacheEvict
:觸發(fā)緩存銷毀。@CachePut
:更新緩存。@Caching
:重組應(yīng)用到方法上的多個緩存操作。@CacheConfig
:類級別共享緩存相關(guān)的通用設(shè)置。
1、@Cacheable注解
正如其名,@Cacheable
注解用來區(qū)分方法執(zhí)行結(jié)果是否應(yīng)該被緩存,如果后續(xù)該方法再次被調(diào)用,方法的執(zhí)行結(jié)果直接從緩存中獲取,而不會調(diào)用實際的方法邏輯。示例如下:
@Cacheable("books") public Book findBook(ISBN isbn) {...}
當然我們也可以指定多個緩存名稱,如果至少一個緩存被命中,那么關(guān)聯(lián)的緩存結(jié)果就會返回,示例如下:
@Cacheable({"books", "isbns"}) public Book findBook(ISBN isbn) {...}
(1) 默認緩存key的生成
因為緩存都是key-value存儲,每次緩存方法的調(diào)用都會被轉(zhuǎn)義為緩存key的訪問。Spring緩存抽象對于key的生成會采用KeyGenerator
來生成,算法如下:
- 如果沒有方法參數(shù),返回
SimpleKey.EMPTY
。 - 如果該方法只有一個參數(shù),返回參數(shù)實例。
- 如果方法不止一個參數(shù),返回包含所有參數(shù)的
SimpleKey
實例。
這種key的生成策略適用于大部分場景,只要方法參數(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); } }
備注:如果要實現(xiàn)自定義key生成策略,需要實現(xiàn)org.springframework.cache.interceptor.KeyGenerator
接口。
(2) 聲明式自定義key生成
目標方法可能會有多個參數(shù),有些參數(shù)可能只應(yīng)用于方法邏輯,而不適合用作key的生成,例如:
@Cacheable("books") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
對于這種情況,@Cacheable
注解有一個key
屬性,通過該屬性可以自定義Key生成。我們也可以使用SPEL(Spring表達式語言)去指定參數(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)
當然,我們也可以通過@Cacheable
注解指定自定義的KeyGenerator
實例,示例如下:
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
備注:key
和keyGenerator
參數(shù)是互斥的,如果兩者都指定會觸發(fā)異常。
(3) 默認緩存解析
Spring緩存抽象通過CacheResolver
去解析操作級別的緩存,而CacheResolver
會用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) 自定義緩存解析
默認緩存解析對于單CacheManager
應(yīng)用適應(yīng)很好,對于有多個緩存管理器的應(yīng)用,我們可以對每個操作設(shè)置緩存管理器,如下:
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") public Book findBook(ISBN isbn) {...}
(5) 條件式緩存
有時方法緩存結(jié)果可能要取決于指定的參數(shù),緩存注解通過支持SPEL
的condition
屬性實現(xiàn)該功能,示例如下:
@Cacheable(cacheNames="book", condition="#name.length() < 32") public Book findBook(String name)
備注:只有當參數(shù)name的長度小于32時,方法結(jié)果才會被緩存。
除了condition
屬性,unless
屬性可以用來決定方法返回值不緩存。與condition
不同,unless
表達式在方法被調(diào)用后才會執(zhí)行,示例如下:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") public Book findBook(String name)
備注:如果Book對象的hardback
屬性為true則不緩存,為false才緩存。
當然,緩存抽象同時也支持java.util.Optional
,只有當Optional
中的值存在時,方法返回值才會被緩存。#result
代表方法的執(zhí)行結(jié)果,上面的我們可以改寫:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback") public Optional<Book> findBook(String name)
備注;#result
引用的始終是Book對象,而不是Optional對象,因為返回值可能為空,所以我們應(yīng)該使用安全導(dǎo)航操作符 => ?.
關(guān)于其它可以用的緩存SpEL表達式上下文,可以參考:Available Caching SpEL Evaluation Context。
2、@CachePut注解
這個注解主要用于更新緩存,也就說帶有該注解的方法總是會執(zhí)行,并且方法的返回值會刷新緩存。該注解和@Cacheable
的參數(shù)相同,示例如下:
@CachePut(cacheNames="book", key="#isbn") public Book updateBook(ISBN isbn, BookDescriptor descriptor)
備注:@CachePut
和@Cacheable
的主要區(qū)別在于后者會通過緩存跳過方法的執(zhí)行,而前者為了更新緩存會迫使方法執(zhí)行。
3、@CacheEvict注解
這個注解主要用來清除緩存,與@Cacheable
注解相反,方法的執(zhí)行會觸發(fā)從緩存中刪除數(shù)據(jù)。@CacheEvit
注解要求指定一個或多個緩存名。除此之外,該注解還有一個額外的屬性allEntries
,指定該屬性值為true后會清除某個緩存名下的所有緩存key。示例如下:
@CacheEvict(cacheNames="books", allEntries=true) public void loadBooks(InputStream batch)
備注:緩存名為books下的所有緩存key都會被清除。
4、@Caching注解
有些情況下,相同類型多個注解,如@CacheEvict
或者@CachePut
需要被指定。@Caching
注解允許多個嵌套@Cacheable
、@CachePut
、@CacheEvict
注解用在同一個方法上。示例如下:
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") }) public Book importBooks(String deposit, Date date)
5、@CacheConfig注解
目前我們已經(jīng)了解到緩存操作提供了很多定制化的選項,然而有些定制化選項如果應(yīng)用到類中的所有操作可能會有些冗余,示例如下:
@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }
@CacheConfig是一個類級別的注解,這個注解可以共享緩存名稱,自定義的KeyGenerator
,自定義的CacheManager
和自定義的CacheResolver
。
方法操作級別的自定義選項總是會重寫@CacheConfig
中的自定義選項。下面是每個緩存操作自定義選項對應(yīng)的3個級別,優(yōu)先級從上至下越來越高。
- 全局配置的
CacheManager
,KeyGenerator
等。 - 類級別,通過
@CacheConfig
指定。 - 方法操作級別。
三、開啟聲明式緩存注解
直接在配置類上加上#EnableCaching
即可,如下:
@Configuration @EnableCaching public class AppConfig { }
四、使用自定義注解
Spring緩存抽象允許我們用自定義注解去標識什么方法可以觸發(fā)緩存構(gòu)建或者消除。@Cacheable
, @CachePut
, @CacheEvict
and @CacheConfig
這些注解都可以作為元注解,其實即使可以修飾其它注解,示例如下:
@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容器會在運行時識別并且知道它是用來干嘛的。
備注:后面我們會利用自定義注解實現(xiàn)自定義過期時間的緩存方案。
到此這篇關(guān)于Spring基于注解的緩存聲明深入探究的文章就介紹到這了,更多相關(guān)Spring注解的緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IntelliJ IDEA 2019.1.1 for MAC 下載和注
這篇文章主要介紹了IntelliJ IDEA 2019.1.1 for MAC 下載和注冊碼激活,教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04sqlserver和java將resultSet中的記錄轉(zhuǎn)換為學(xué)生對象
這篇文章主要介紹了如何利用sqlserver和java將resultSet中的記錄轉(zhuǎn)換為學(xué)生對象,附有超詳細的代碼,需要的朋友可以參考一下,希望對你有所幫助2021-12-12Java中transient關(guān)鍵字的詳細總結(jié)
本文要介紹的是Java中的transient關(guān)鍵字,transient是短暫的意思。對于transient 修飾的成員變量,在類的實例對象的序列化處理過程中會被忽略,感興趣的朋友可以參考閱讀2023-04-04java實現(xiàn)文件分片上傳并且斷點續(xù)傳的示例代碼
本文主要介紹了java實現(xiàn)文件分片上傳并且斷點續(xù)傳的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2023-05-05