對於緩存聲明,Spring的緩存抽象提供了一組Java註解:html
@Cacheable
:觸發緩存人口。@CacheEvict
:觸發緩存驅逐。@CachePut
:在不影響方法執行的狀況下更新緩存。@Caching
:從新組合要應用於方法的多個緩存操做。@CacheConfig
:在類級別上共享一些常見的緩存相關設置。@Cacheable
註解顧名思義,你可使用@Cacheable
來劃分可緩存的方法 — 也就是說,將結果存儲在緩存中的方法,以便在後續調用(具備相同的參數)時返回緩存中的值,而沒必要實際執行該方法。在其最簡單的形式中,註解聲明須要與帶註解的方法相關聯的緩存的名稱,以下面的示例所示:java
@Cacheable("books") public Book findBook(ISBN isbn) {...}
在前面的代碼片斷中,findBook
方法與名爲books
的緩存相關聯,每次調用該方法時,都會檢查緩存,以查看是否已經執行了調用,而且不須要重複調用。雖然在大多數狀況下,只聲明一個緩存,可是註解容許指定多個名稱,以便使用多個緩存。在本例中,在執行方法以前檢查每一個緩存 — 若是至少命中一個緩存,則返回關聯的值。算法
全部其餘不包含該值的緩存也會被更新,即便緩存的方法並無實際執行。
下面的示例在findBook
方法上使用@Cacheable
:spring
@Cacheable({"books", "isbns"}) public Book findBook(ISBN isbn) {...}
因爲緩存本質上是鍵值存儲,所以每次對緩存方法的調用都須要轉換爲適合緩存訪問的鍵,緩存抽象使用基於如下算法的簡單KeyGenerator
:segmentfault
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)
key
和keyGenerator
參數是互斥的,一個操做指定二者會致使異常。
緩存抽象使用簡單的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
均可以提供此特定信息。與
key
和keyGenerator
相似,cacheManager
和cacheResolver
參數是互斥的,指定這二者的操做會致使異常,由於CacheResolver
實現忽略了自定義CacheManager
,這可能不是你所指望的。
在多線程環境中,可能會爲同一參數同時調用某些操做(一般在啓動時),默認狀況下,緩存抽象不會鎖定任何內容,而且可能會屢次計算相同的值,從而沒法實現緩存。
對於這些特定狀況,你可使用sync
屬性指示底層緩存提供程序在計算值時鎖定緩存條目,所以,只有一個線程忙於計算該值,而其餘線程則被阻塞,直到該條目在緩存中更新爲止,如下示例顯示瞭如何使用sync
屬性:
@Cacheable(cacheNames="foos", sync=true) public Foo executeExpensiveOperation(String id) {...}
這是一項可選功能,你最喜歡的緩存庫可能不支持它,核心框架提供的全部
CacheManager
實現都支持它,有關更多詳細信息,請參閱緩存提供程序的文檔。
有時,方法可能不適合一直緩存(例如,它可能取決於給定的參數),緩存註解經過condition
參數支持此類功能,該參數採用被評估爲true
或false
的SpEL
表達式,若是爲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
表達式都針對專用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 表達式、緩存放置表達式(計算鍵)或緩存逐出表達式(當 beforeInvocation 爲false 時)中使用 對於受支持的包裝器(例如 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上
的自定義集,所以,這爲每一個緩存操做提供了三個級別的自定義:
CacheManager
、KeyGenerator
。@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 | 使用配置的cacheManager 的SimpleCacheResolver |
用於解析後備緩存的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-class 爲false 或者省略了該屬性,則會建立基於標準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部分。
方法可見性和緩存註解
使用代理時,應僅將緩存註解應用於具備公共可見性的方法,若是使用這些註解來註解
protected
、private
或包可見方法,不會引起錯誤,但帶註解的方法不會顯示已配置的緩存設置。若是須要註解非公共方法,請考慮使用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的行爲。