Spring Framework 參考文檔(聲明式基於註解的緩存)

聲明式基於註解的緩存

對於緩存聲明,Spring的緩存抽象提供了一組Java註解:html

  • @Cacheable:觸發緩存人口。
  • @CacheEvict:觸發緩存驅逐。
  • @CachePut:在不影響方法執行的狀況下更新緩存。
  • @Caching:從新組合要應用於方法的多個緩存操做。
  • @CacheConfig:在類級別上共享一些常見的緩存相關設置。

@Cacheable註解

顧名思義,你可使用@Cacheable來劃分可緩存的方法 — 也就是說,將結果存儲在緩存中的方法,以便在後續調用(具備相同的參數)時返回緩存中的值,而沒必要實際執行該方法。在其最簡單的形式中,註解聲明須要與帶註解的方法相關聯的緩存的名稱,以下面的示例所示:java

@Cacheable("books")
public Book findBook(ISBN isbn) {...}

在前面的代碼片斷中,findBook方法與名爲books的緩存相關聯,每次調用該方法時,都會檢查緩存,以查看是否已經執行了調用,而且不須要重複調用。雖然在大多數狀況下,只聲明一個緩存,可是註解容許指定多個名稱,以便使用多個緩存。在本例中,在執行方法以前檢查每一個緩存 — 若是至少命中一個緩存,則返回關聯的值。算法

全部其餘不包含該值的緩存也會被更新,即便緩存的方法並無實際執行。

下面的示例在findBook方法上使用@Cacheablespring

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

默認鍵生成

因爲緩存本質上是鍵值存儲,所以每次對緩存方法的調用都須要轉換爲適合緩存訪問的鍵,緩存抽象使用基於如下算法的簡單KeyGeneratorsegmentfault

  • 若是沒有提供參數,則返回SimpleKey.EMPTY
  • 若是隻給出一個參數,則返回該實例。
  • 若是給定多個參數,則返回包含全部參數的SimpleKey

這種方法適用於大多數用例,只要參數具備天然鍵並實現有效的hashCode()equals()方法,若是不是這樣,你須要改變策略。api

要提供不一樣的默認鍵生成器,你須要實現org.springframework.cache.interceptor.KeyGenerator接口。數組

默認的鍵生成策略隨着Spring 4.0的發佈而改變,Spring的早期版本使用的鍵生成策略,對於多個鍵參數,只考慮參數的 hashCode(),而不考慮 equals(),這可能會致使意想不到的鍵衝突(有關背景,請參見 SPR-10237),新的 SimpleKeyGenerator爲這些場景使用複合鍵。

若是你想繼續使用前面的鍵策略,能夠配置已廢棄的org.springframework.cache.interceptor.DefaultKeyGenerator類,或者建立一個基於散列的自定義KeyGenerator實現。緩存

自定義鍵生成聲明

因爲緩存是通用的,所以目標方法極可能具備各類簽名,這些簽名沒法輕鬆映射到緩存結構之上,當目標方法有多個參數時,這每每會變得明顯,其中只有一些參數適用於緩存(其他參數僅由方法邏輯使用),請考慮如下示例:安全

@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

乍一看,雖然兩個boolean參數影響了書的發現方式,但它們對緩存沒有用處,若是兩個中只有一個重要而另外一個不重要怎麼辦?多線程

對於這種狀況,@Cacheable註解容許你經過其key屬性指定鍵的生成方式,你可使用SpEL選擇感興趣的參數(或其嵌套屬性),執行操做,甚至調用任意方法,而無需編寫任何代碼或實現任何接口。這是默認生成器的推薦方法,由於隨着代碼庫的增加,簽名方法每每會有很大不一樣,雖然默認策略可能適用於某些方法,但它不多適用於全部方法。

如下示例是各類SpEL聲明:

@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)

前面的代碼片斷顯示了選擇某個參數、其屬性之1、甚至是任意(靜態)方法是多麼容易。

若是負責生成鍵的算法太具體或須要共享,則能夠在操做上定義自定義keyGenerator,爲此,請指定要使用的KeyGenerator bean實現的名稱,如如下示例所示:

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
keykeyGenerator參數是互斥的,一個操做指定二者會致使異常。

默認的緩存解析

緩存抽象使用簡單的CacheResolver,它使用配置的CacheManager檢索在操做級別定義的緩存。

要提供不一樣的默認緩存解析器,你須要實現org.springframework.cache.interceptor.CacheResolver接口。

自定義緩存解析

默認緩存解析很是適合使用單個CacheManager而且沒有複雜緩存解析要求的應用程序。

對於使用多個緩存管理器的應用程序,能夠將cacheManager設置爲用於每一個操做,如如下示例所示:

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") 
public Book findBook(ISBN isbn) {...}

你也能夠徹底替換CacheResolver,方式相似於替換鍵生成,爲每一個緩存操做請求解析,讓實現實際上根據運行時參數解析要使用的緩存,如下示例顯示如何指定CacheResolver

@Cacheable(cacheResolver="runtimeCacheResolver") 
public Book findBook(ISBN isbn) {...}
從Spring 4.1開始,緩存註解的 value屬性再也不是必需的,由於不管註解的內容如何,​​ CacheResolver均可以提供此特定信息。

keykeyGenerator相似,cacheManagercacheResolver參數是互斥的,指定這二者的操做會致使異常,由於CacheResolver實現忽略了自定義CacheManager,這可能不是你所指望的。

同步緩存

在多線程環境中,可能會爲同一參數同時調用某些操做(一般在啓動時),默認狀況下,緩存抽象不會鎖定任何內容,而且可能會屢次計算相同的值,從而沒法實現緩存。

對於這些特定狀況,你可使用sync屬性指示底層緩存提供程序在計算值時鎖定緩存條目,所以,只有一個線程忙於計算該值,而其餘線程則被阻塞,直到該條目在緩存中更新爲止,如下示例顯示瞭如何使用sync屬性:

@Cacheable(cacheNames="foos", sync=true) 
public Foo executeExpensiveOperation(String id) {...}
這是一項可選功能,你最喜歡的緩存庫可能不支持它,核心框架提供的全部 CacheManager實現都支持它,有關更多詳細信息,請參閱緩存提供程序的文檔。

條件緩存

有時,方法可能不適合一直緩存(例如,它可能取決於給定的參數),緩存註解經過condition參數支持此類功能,該參數採用被評估爲truefalseSpEL表達式,若是爲true,則緩存該方法,若是沒有,它的行爲就好像該方法沒有被緩存(也就是說,不管緩存中的值是什麼,或者使用了什麼參數,每次都執行該方法)。例如,僅當參數name的長度小於32時,纔會緩存如下方法:

@Cacheable(cacheNames="book", condition="#name.length() < 32") 
public Book findBook(String name)

condition參數外,還可使用unless參數否決向緩存添加值,與condition不一樣,unless表達式在調用該方法後進行評估,要擴展上一個示例,也許咱們只想緩存平裝書,以下例所示:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") 
public Book findBook(String name)

緩存抽象支持java.util.Optional,僅在其存在時將其內容用做緩存值,#result老是引用業務實體而從不支持包裝器,所以前面的示例能夠重寫以下:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)

請注意,result仍然引用Book而不是Optional,因爲它可能爲null,咱們應該使用安全導航操做符。

可用的緩存SpEL評估上下文

每一個SpEL表達式都針對專用context進行評估,除了內置參數以外,框架還提供專用的與緩存相關的元數據,例如參數名稱。下表描述了上下文可用的項目,以便你能夠將它們用於鍵和條件計算:

名稱 位置 描述 示例
methodName 根對象 要調用的方法的名稱 #root.methodName
method 根對象 正在調用的方法 #root.method.name
target 根對象 正在調用的目標對象 #root.target
targetClass 根對象 正在調用的目標的類 #root.targetClass
args 根對象 用於調用目標的參數(做爲數組) #root.args[0]
caches 根對象 執行當前方法的高速緩存的集合 #root.caches[0].name
參數名稱 評估上下文 任何方法參數的名稱;
若是名稱不可用(可能因爲沒有調試信息),
參數名稱也能夠在#a<#arg>下得到。
其中#arg表明參數索引(從0開始)。
#iban#a0
(你也可使用#p0
#p<#arg>表示法做爲別名)
result 評估上下文 方法調用的結果(要緩存的值)
只能在unless表達式、緩存放置表達式(計算鍵)
或緩存逐出表達式(當beforeInvocationfalse
時)中使用
對於受支持的包裝器(例如Optional),
#result引用實際的對象,而不是包裝器。
#result

@CachePut註解

當須要更新緩存而不干擾方法執行時,可使用@CachePut註解,也就是說,始終執行該方法,並將其結果放入緩存中(根據@CachePut選項)。它支持與@Cacheable相同的選項,應該用於緩存填充而不是方法流優化,如下示例使用@CachePut註解:

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
一般強烈建議不要在同一方法上使用 @CachePut@Cacheable註解,由於它們有不一樣的行爲,雖而後者致使經過使用緩存跳過方法執行,但前者強制執行以執行緩存更新。這將致使意想不到的行爲,除了特定的狀況外(例如註解具備將它們彼此排除的條件)以外,應避免此類聲明。還請注意,這些條件不該該依賴於 result對象(即 #result變量),由於這些都是預先驗證以確認排除的。

@CacheEvict註解

緩存抽象不只容許緩存存儲的填充,還容許驅逐,此過程對於從緩存中刪除陳舊或未使用的數據很是有用。與@Cacheable相反,@CacheEvict劃分了執行緩存逐出的方法(即,用做從緩存中刪除數據的觸發器的方法)。@CacheEvict須要指定受操做影響的一個或多個緩存,容許指定自定義緩存和鍵解析或條件,並具備一個額外的參數(allEntries),指示是否須要執行緩存範圍的驅逐而不只僅是條目驅逐(基於鍵),如下示例逐出books緩存中的全部條目:

@CacheEvict(cacheNames="books", allEntries=true) 
public void loadBooks(InputStream batch)

當須要清除整個緩存區域時,此選項會派上用場,而不是逐出每一個條目(這將花費很長時間,由於它是低效的),全部條目在一個操做中被移除,如前面的示例所示。請注意,框架會忽略此方案中指定的任何鍵,由於它不適用(整個緩存被驅逐,而不只僅是一個條目)。

你還能夠經過使用beforeInvocation屬性指示驅逐是在方法執行以後(默認)仍是在方法執行以前進行的,前者提供與其餘註解相同的語義:方法成功完成後,將執行緩存上的操做(在本例中爲驅逐),若是方法未執行(由於它可能被緩存)或拋出異常,則不會發生驅逐。後者(beforeInvocation=true)致使驅逐始終在調用方法以前發生,這在驅逐不須要與方法結果相關聯的狀況下很是有用。

請注意,void方法能夠與@CacheEvict一塊兒使用 — 由於方法充當觸發器,返回值將被忽略(由於它們不與緩存交互),這不是@Cacheable的狀況,它將數據添加或更新到緩存中,所以須要結果。

@Caching註解

有時,須要指定相同類型的多個註解(例如@CacheEvict@CachePut),例如,由於條件或鍵表達式在不一樣的緩存之間是不一樣的,@Caching容許在同一方法上使用多個嵌套的@Cacheable @CachePut@CacheEvict註解,如下示例使用兩個@CacheEvict註解:

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

@CacheConfig註解

到目前爲止,咱們已經看到緩存操做提供了許多自定義選項,你能夠爲每一個操做設置這些選項,可是,若是某些自定義選項適用於該類的全部操做,那麼配置它們可能會很麻煩。例如,能夠用一個類級別的定義替換指定類的每一個緩存操做使用的緩存名稱,這就是@CacheConfig發揮做用的地方,如下示例使用@CacheConfig設置緩存的名稱:

@CacheConfig("books") 
public class BookRepositoryImpl implements BookRepository {

    @Cacheable
    public Book findBook(ISBN isbn) {...}
}

@CacheConfig是一個類級註解,容許共享緩存名稱、自定義KeyGenerator、自定義CacheManager和自定義CacheResolver,將此註解放在類上不會打開任何緩存操做。

操做級別自定義始終覆蓋@CacheConfig上的自定義集,所以,這爲每一個緩存操做提供了三個級別的自定義:

  • 全局配置,可用於CacheManagerKeyGenerator
  • 在類級別,使用@CacheConfig
  • 在操做級別。

啓用緩存註解

請務必注意,即便聲明緩存註解也不會自動觸發其操做 — 與Spring中的許多功能同樣,該特性必須以聲明方式啓用(這意味着若是你懷疑緩存是罪魁禍首,你能夠經過僅刪除一個配置行而不是代碼中的全部註解來禁用它)。

要啓用緩存註解,請將註解@EnableCaching添加到@Configuration類之一:

@Configuration
@EnableCaching
public class AppConfig {
}

或者,對於XML配置,你可使用cache:annotation-driven元素:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">

        <cache:annotation-driven/>
</beans>

cache:annotation-driven元素和@EnableCaching註解都容許你指定各類選項,這些選項影響方式經過AOP將緩存行爲添加到應用程序,該配置與@Transactional的配置有意相似。

處理緩存註解的默認建議模式是 proxy,它容許僅經過代理攔截調用,同一類中的本地調用不能以這種方式截獲,對於更高級的攔截模式,請考慮結合編譯時或加載時編織切換到 aspectj模式。
有關實現 CachingConfigurer所需的高級自定義(使用Java配置)的更多詳細信息,請參閱 javadoc
緩存註解設置
XML屬性 註解屬性 默認 描述
cache-manager N/A(參見CachingConfigurer javadoc cacheManager 要使用的緩存管理器的名稱;
使用此緩存管理器(若是未設置則爲cacheManager)在後臺初始化默認的CacheResolver
要得到更精細的緩存解析管理,請考慮設置「cache-resolver」屬性。
cache-resolver N/A(參見CachingConfigurer javadoc 使用配置的cacheManagerSimpleCacheResolver 用於解析後備緩存的CacheResolver的bean名稱;
此屬性不是必需的,只須要指定爲「cache-manager」屬性的替代。
key-generator N/A(參見CachingConfigurer javadoc SimpleKeyGenerator 要使用的自定義鍵生成器的名稱。
error-handler N/A(參見CachingConfigurer javadoc SimpleCacheErrorHandler 要使用的自定義緩存錯誤處理程序的名稱;
默認狀況下,在緩存相關操做期間拋出的任何異常都會在客戶端返回。
mode mode proxy 默認模式(proxy)處理要使用Spring的AOP框架代理的帶註解bean(遵循代理語義,如前所述,僅適用於經過代理進入的方法調用);
替代模式(aspectj)用Spring的AspectJ緩存切面編織受影響的類,修改目標類字節碼以應用於任何類型的方法調用;
AspectJ編織須要在類路徑中使用spring-aspects.jar以及啓用加載時編織(或編譯時編織);
(有關如何設置加載時編織的詳細信息,請參閱Spring配置)。
proxy-target-class proxyTargetClass false 僅適用於代理模式;
控制爲使用@Cacheable@CacheEvict註解註解的類建立哪一種類型的緩存代理;
若是proxy-target-class屬性設置爲true,則建立基於類的代理;
若是proxy-target-classfalse或者省略了該屬性,則會建立基於標準JDK接口的代理;
(有關不一樣代理類型的詳細解釋,請參閱代理機制)。
order order Ordered.LOWEST_PRECEDENCE 定義應用於使用@Cacheable@CacheEvict註解的bean的緩存建議的順序;
(有關排序AOP建議相關規則的更多信息,請參閱建議排序);
沒有指定的排序意味着AOP子系統肯定建議的順序。
<cache:annotation-driven/>查找 @Cacheable/@CachePut/@CacheEvict/@Caching僅在定義它的同一應用程序上下文中的bean上進行緩存,這意味着,若是你在 WebApplicationContext中爲 DispatcherServlet放置 <cache:annotation-driven/>,它只會在你的控制器中檢查bean,而不是你的服務,有關更多信息,請參閱MVC部分。

方法可見性和緩存註解

使用代理時,應僅將緩存註解應用於具備公共可見性的方法,若是使用這些註解來註解protectedprivate或包可見方法,不會引起錯誤,但帶註解的方法不會顯示已配置的緩存設置。若是須要註解非公共方法,請考慮使用AspectJ(請參閱本節的其他部分),由於它會更改字節碼自己。

Spring建議只使用 @Cache*註解來註解具體類(以及具體類的方法),而不是註解接口,你固然能夠將 @Cache*註解放在接口(或接口方法)上,但這隻能在你使用基於接口的代理時按預期工做。Java註解不是從接口繼承的事實意味着,若是你使用基於類的代理( proxy-target-class="true")或基於編織的切面( mode="aspectj"),代理和編織基礎設施沒法識別緩存設置,而且該對象未包裝在緩存代理中。
在代理模式(默認)下,只攔截經過代理進入的外部方法調用,這意味着自調用(其實是目標對象中調用目標對象的另外一個方法的方法)在運行時不會致使實際的緩存,即便調用的方法用 @Cacheable標記,在這種狀況下,請考慮使用 aspectj模式。此外,必須徹底初始化代理以提供預期的行爲,所以你不該該在初始化代碼(即 @PostConstruct)中依賴此功能。

使用自定義註解

自定義註解和AspectJ

此特性僅適用於基於代理的方法,但能夠經過使用AspectJ進行一些額外的工做。
spring-aspects模塊僅定義標準註解的切面,若是你已定義本身的註解,則還須要爲這些註解定義切面。

緩存抽象容許你使用本身的註解來標識觸發緩存填充或驅逐的方法,這做爲模板機制很是方便,由於它消除了重複緩存註解聲明的須要,這在指定了鍵或條件或者代碼庫中不容許外部導入(org.springframework)時特別有用。與其餘原型註解相似,你可使用@Cacheable@CachePut@CacheEvict@CacheConfig做爲元註解(便可以註解其餘註解的註解),在下面的示例中,使用本身的自定義註解替換常見的@Cacheable聲明:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface SlowService {
}

在前面的示例中,定義了本身的SlowService註解,該註解自己使用@Cacheable進行註解,如今咱們能夠替換如下代碼:

@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註解,但容器會在運行時自動獲取其聲明並理解其含義,請注意,如前所述,須要啓用annotation-driven的行爲。


上一篇:理解緩存抽象

相關文章
相關標籤/搜索