Spring-5-精品翻譯:緩存抽象(Cache-Abstraction)

32.1 介紹

Spring 框架從 3.1 開始,對 Spring 應用程序提供了透明式添加緩存的支持。和事務支持同樣,抽象緩存容許一致地使用各類緩存解決方案,並對代碼的影響最小。html

從 4.1 版本開始,緩存抽象支持了 JSR-107 註釋和更多自定義選項,從而獲得了顯著的改進。java

32.2 緩存抽象

緩存(Cache) vs 緩衝區(Buffer)

緩存和緩衝區兩個術語每每能夠互換着使用。但注意,它們表明着不一樣的東西。
緩衝區是做用於快和慢速實體之間的中間臨時存儲。
一塊緩衝區必須等待其餘並影響性能,經過容許一次性移動整個數據塊而不是小塊來緩解。數據從緩衝區讀寫只有一次。所以緩衝區對至少一方是可見的。
另外一方面,緩存根據定義是隱性的,雙方不會知道緩存的發生。它提升了性能,但容許以快速的方式屢次讀取相同的數據。

想了解更多這兩者之間的差別,見:https://en.wikipedia.org/wiki/Cache_(computing)#The_difference_between_buffer_and_cache

核心上,抽象將緩存做用於 Java 方法上,基於緩存中的可用信息,能夠減小方法的執行次數。也就是說,每次目標方法的調用時,抽象使用緩存行爲來檢查執行方法,檢查執行方法是否給定了緩存的執行參數:若是有,則返回緩存結果,不執行具體方法;若是沒有,則執行方法,並將結果緩存後,返回給用戶。以便於下次調用方法時,直接返回緩存的結果。這樣,只要給定緩存執行參數,在複雜的方法(不管是 CPU 或者 IO 相關)只須要執行一次,就能夠獲得結果,並利用緩存可重複使用結果,而沒必要再次執行該方法。另外,緩存邏輯能夠被透明地調用,不會對調用者形成任何的困擾。git

顯然,這種方法只適用於爲某個給定輸入(或參數)返回相同輸出(結果),不管執行多少次。

抽象提供的其餘緩存相關操做,好比更新緩存內容或者刪除其中一條緩存。若是在應用程序過程當中,發生了變化的數據須要緩存,那這些功能會頗有用。github

好比 Spring 框架其餘服務同樣,緩存服務是一種抽象(不是緩存的實現),而且須要使用實際的存儲器來存儲緩存數據。也就是說,抽象可以使開發人員沒必要編寫緩存邏輯,但它沒有提供緩存的存儲器。這個抽象是由 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口實現的。算法

有些抽象的實現是開箱即用的:基於 JDK java.util.concurrent.ConcurrentMap 緩存實現,Ehcache 2.x,Gemfire cache, Caffeine 和 JSR-107 緩存(例如 Ehcache 3.x)。有關緩存存儲/提供的更多信息,請參見 32.7 節 《Plugging-in different back-end caches》。spring

緩存抽象沒有特別處理多線程和多進程環境,由於這些功能由緩存實現來處理...

若是是多進程環境(即部署在多個節點上的應用程序),則須要相應的配置程序提供緩存。根據使用狀況,幾個節點上相同數據的副本可能足夠多了,但若是在應用程序過程當中更改了數據,則須要啓動其餘傳播機制,進行同步緩存數據。數據庫

緩存一個特定的對象是典型的緩存交互 get-if-not-found-then-proceed-and-put-finally 代碼塊:不需應用任何鎖,而且幾個線程同時嘗試加載相同的對象。一樣適用於回收,若是多個線程同時更新或者回收數據,則可能會使用過期的數據。某些緩存提供者在該領域提供高級功能,請參考您正在使用的緩存提供者的更多高級功能的詳細信息。express

要使用緩存抽象,開發人員須要注意兩個方面:後端

  • 緩存聲明 - 標誌緩存的方法及緩存策略
  • 緩存配置 - 數據讀寫的緩存數據庫

32.3 基於聲明式註解的緩存

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

  • @Cacheable 觸發緩存機制
  • @CacheEvict 觸發緩存回收
  • @CachePut 更新緩存,而不會影響方法的執行
  • @Caching 組合多個緩存操做到一個方法
  • @CacheConfig 類級別共享系誒常見的緩存相關配置

下面,讓咱們仔細看看每一個註釋。

32.3.1 @Cacheable 註解

顧名思義,@Cacheable 用於標識可緩存的方法 - 即須要將結果存儲到緩存中的方法,以便於再一次調用(具備相同的輸入參數)時返回緩存結果,而無需執行該方法。在最簡單的形式中,註解聲明須要定義與註解方法相關聯的緩存名稱:

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

在上面的代碼中,findBook 與名爲 books 的緩存相關。每次調用該方法時,都會檢查緩存以查看調用是否已經被執行,而且沒必要重複。而在大多數狀況下,只有一個緩存被聲明,註解容許指定多個名稱,以便使用多個緩存。在這種狀況下,執行該方法以前將檢查每一個高速緩存 - 若是至少有一個緩存被命中,則返回緩存相關的值:

注意:即便沒有實際執行緩存方法,全部其餘不包含該值的緩存也將被更新。
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

默認鍵生成

緩存的本質是鍵值對存儲,因此每次調用緩存方法都會轉換做用於緩存訪問合適的鍵。開箱即用,緩存抽象使用基於如下算法的簡單 KeyGenerator:

  • 若是沒有參數,返回 SimpleKey.EMPTY
  • 若是隻有一個參數,返回該實例
  • 若是大於一個參數,返回一個包含全部參數的 SimpleKey

這種算法對大多數用例很適用,只要參數具備天然鍵並實現了有效的 hashCode()equals() 方法。若是不是這樣,策略就須要改變。
要提供不一樣的默認密鑰生成器,須要實現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 參數影響了該 findBook 方法,但對緩存沒有任何用處。更進一步,若是二者之中只有一個是重要的,另外一個不重要呢?

這種狀況下,@Cacheable 註解只容許用戶經過鍵屬性指定 key 的生成方式。開發人員可使用 SpEL 來選擇須要的參數(或其嵌套屬性),執行參數設置調用任何方法,無需編寫任何代碼或者實現人任何接口。這是默認鍵生成器推薦的方法,由於方法在代碼庫的增加下,會有徹底不一樣的方法實現。而默認策略可能適用於某些方法,並非適用於全部的方法。

這裏是 SpEL 聲明的一些示例 - 若是你不熟悉它,查閱Chapter 6, Spring Expression Language (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)

上面代碼片斷顯示了選擇某個參數,或參數的某個屬性值或任意(靜態)方法,如此方便操做。

若是生成鍵的算法太具體或者須要共享,能夠操做中定義一個自定定義的 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。每一個緩存操做都要求緩存解析,基於運行時參數的緩存解析:

@Cacheable(cacheResolver="runtimeCacheResolver")
public Book findBook(ISBN isbn) {...}
自 Spring 4.1 之後,緩存註解的屬性值是沒必要要的,由於 CacheResolver 能夠提供該特定的信息,不管註解的內容是什麼。與 key 和 keyGenerator 相似,cacheManager 和 cacheResolver 參數是互斥的,而且指定二者一樣的操做會致使異常,由於 CacheResolver 的實現將忽略自定義的 CacheManager。這是你不但願的。

同步緩存

在多線程環境中,某些操做可能會致使同時引用相同的參數(一般在啓動時)。默認狀況下,緩存抽象不會鎖定任何對象,一樣的對象可能會被計算好幾回,從而達不到緩存的目的。

對於這些特熟狀況,sync 屬性可用於指示底層緩存提供程序在計算該值時鎖定緩存對象。所以,只有一個線程將忙於計算值,而其餘線程會被阻塞,直到該緩存對象被更新爲止。

@Cacheable(cacheNames="foos", sync="true")
public Foo executeExpensiveOperation(String id) {...}
這是可選功能,可能你是用的緩存庫不支持它。由核心框架提供的全部 CacheManager 都會實現並支持它。翻閱緩存提供商文檔能夠了解更多詳細信息。

條件緩存

有時,一種方法可能不適合緩存(例如,它可能取決於給定的參數)。緩存註解經過條件參數支持這樣的功能,採用使用 SpEL 表達式的 condition 參數表示。若是是 true,則緩存方法,若是是 false,則不緩存,不管緩存中有什麼值或者使用了哪些參數都嚴格按照規則執行該方法。一個快速入門的例子 - 只有當參數名稱長度小於 32 的時候,纔會緩存下面方法:

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

另外,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)

注意:結果仍然是 Book 不是 Optional

緩存 SpEL 上下文

每一個 SpEL 表達式都會有求值上下文。除了構建參數,框架提供了專門的緩存相關元數據,好比參數名稱。下表列出了可用於上下文的項目,所以可使用他們進行鍵和條件的計算:

表 32.1 緩存 SpEL 元數據

參數名 用處 描述 例子
methodName root object 被調用的方法名 #root.methodName
method root object 被調用的方法 #root.method.name
target root object 被調用的對象 #root.target
targetClass root object 被調用的類 #root.targetClass
args root object 被調用的的類目標參數 #root.args[0]
caches root object 當前方法執行的緩存列表 #root.caches[0].name
argument name evaluation context 任何方法參數的名稱 #iban#a0 (也可使用 #p0 或者 #p<#arg>)
result evaluation context 方法調用的結果(緩存的值) #result

32.3.2 @CachePut 註解

須要緩存更新但不影響方法執行的狀況,可使用 @CachePut 註解。也就是說,該方法始終執行,並將其結果放入緩存中(根據 @CachePut 選項)。它支持與 @Cacheable 相同的選項,適用於緩存而不是方法流程優化:

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
對同一個方法同時使用 @CachePut@Cacheable 註解一般是不推薦的。由於它們有不一樣的行爲。後者經過使用緩存並跳過方法執行,前者強制執行方法並進行緩存更新。這會致使意想不到的行爲,除了具體的場景(例如須要排除條件的註解),應該避免這樣的聲明方式。還要注意,這樣的條件不該該依賴於結果對象(#result 對象),由於結果對象應該在前面被驗證後排除。

32.3.3 @CacheEvict 註解

緩存抽象不只僅緩存更多數據,還能夠回收緩存。這個過程對於從緩存中刪除舊數據或者未使用的數據很是有用。與 @Cacheable 相反, 註解 @CacheEvict 劃分了回收緩存的方法,即做爲從緩存中刪除數據的觸發器方法。@CacheEvict 須要指定一個(或者多個)執行動做,而且容許自定義緩存和鍵解析或條件被指定。但有一個額外的參數 allEntries,能夠指示是否全部對象都要被收回,仍是一個對象(取決於key)。

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

當整個緩存區須要被收回時,這個選項就會派上用場 - 而不是回收每一個對象(當不起做用時會須要很長的響應時間),因此對象會在一個操做中被刪除,如上所示。注意,框架將忽略此場景下指定的任何key,由於它不適用(整個緩存收回,不只僅是一個條目被收回)。

還能夠指出發生緩存回收是在默認以後仍是在經過 beforeInvocation 屬性執行方法後。前者提供與其餘註解相同的語義 - 一旦方法成功完成,就執行緩存上的動做(這種狀況是回收)。若是方法不執行(由於它可能會被緩存)或者拋出異常,則不會發生回收。後者(beforeInvocation = ture)會致使在調用該方法以前發生回收 - 在驅逐不須要與方法結果相關的狀況下,這是有用的。

重要的是,void 方法能夠和 @CacheEvict 一塊兒使用 - 因爲方法做爲觸發器,返回值會被忽略(由於他們不與緩存交互) - @Cacheable 就不是這樣的,它添加 / 更新數據到緩存時,須要一個結果。

32.3.4 @Caching 註解

另外狀況下,須要指定想同類型的多個註解,例如 @CacheEvict@CachePut 須要被指定。好比由於不一樣緩存之間的條件或者鍵表達式不一樣。@Caching 容許在同一個方法上使用多個嵌套的 @Cacheable@CachePut@CacheEvict

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

32.3.5 @CacheConfig 註解

到目前爲止,咱們已經看到緩存操做提供了許多定製選項,這些選項能夠在操做的基礎上進行設置。 可是,一些自定義選項可能很麻煩,能夠配置是否適用於該類的全部操做。 例如,指定用於該類的每一個緩存操做的高速緩存的名稱能夠被單個類級別定義所替代。 這是@CacheConfig發揮做用的地方。

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

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

@CacheConfig 是一個類級別的註解,能夠共享緩存名稱。自定義 KeyGenerator,自定義 CacheManager 和自定義 CacheResolver。該註解在類上不會操做任何緩存。

操做級別上的自定義會覆蓋 @CacheConfig 的自定義。所以,每一個緩存操做都會有三個級別的定義:

  • 全局配置,可用於 CacheManagerKeyGenerator
  • 類級別,用 @CacheConfig
  • 操做級別層面

@EnableCaching 註解

重點注意的是,即便聲明緩存註解也不會主動觸發動做 - 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 http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

        <cache:annotation-driven />

</beans>

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

Java 高級自定義配置須要實現 CachingConfigurer。更多詳細信息,參考 javadoc。

表 32.2. 緩存註解設置

XML 屬性 註解屬性 默認 描述
cache-manager N/A(參閱 CachingConfigurer javadocs) cacheManager 要使用的緩存管理器的名稱。 使用該緩存管理器(或「cacheManager」未設置)將在幕後初始化默認CacheResolver。 要更精細地管理緩存解析度,請考慮設置「緩存解析器」屬性。
cache-resolver N/A(參閱 CachingConfigurer javadocs) 配置 cacheManager SimpleCacheResolver 要用於解析後備緩存的CacheResolver的bean名稱。 此屬性不是必需的,只須要指定爲「cache-manager」屬性的替代方法。
key-generator N/A(參閱 CachingConfigurer javadocs) SimpleKeyGenerator 要使用的自定義鍵生成器的名稱。
error-handler N/A(參閱 CachingConfigurer javadocs) SimpleCacheErrorHandler 要使用的自定義緩存錯誤處理程序的名稱。 默認狀況下,在緩存相關操做期間拋出任何異常拋出在客戶端。
mode mode proxy 默認模式「代理」使用Spring的AOP框架處理帶註釋的bean(如下代理語義,如上所述,適用於僅經過代理進行的方法調用)。 替代模式「aspectj」代替使用Spring的AspectJ緩存方面編寫受影響的類,修改目標類字節碼以應用於任何類型的方法調用。 AspectJ編織須要類路徑中的spring-aspects.jar以及啓用加載時編織(或編譯時編織)。 (有關如何設置加載時編織的詳細信息,請參閱 the section called 「Spring configuration」
proxy-target-class proxyTargetClass false 僅適用於代理模式。 控制爲使用@Cacheable@CacheEvict註釋註釋的類建立的緩存代理類型。 若是proxy-target-class屬性設置爲true,則會建立基於類的代理。 若是proxy-target-classfalse,或者若是屬性被省略,則會建立基於標準的基於JDK接口的代理。 (有關詳細檢查不一樣代理類型,請參見第Section 7.6, 「Proxying mechanisms」
order order Ordered.LOWEST_PRECEDENCE 定義應用於使用@Cacheable@CacheEvict註釋的bean的緩存通知的順序。 (有關與AOP通知的排序有關的規則的更多信息,請參閱 the section called 「Advice ordering」。)沒有指定的排序意味着AOP子系統肯定建議的順序。
<cache:annotation-driven /> 只會匹配相對應的應用上下文中定義的 @Cacheable / @CachePut / @CacheEvict / @Cached。若是將 <cache:annotation-driven/> 配置在 DispatcherServletWebApplicationContext ,它只檢查控制器中的bean,而不是您的服務。參考 18.2 章節 DispatcherServlet 獲取更多信息。
方法可見性和緩存註解
使用代理後,緩存註解應用於公共可見的方法。若是對 protected、private 或 package-visible 方法進行緩存註解,不會引發錯誤。但註解的方法不會顯示緩存配置。若是更改字節碼時須要註解非公共方法,請考慮使用 AspectJ(見下文)。
Spring 建議只使用 @Cache* 註解具體的類(或具體的方法),而不是註解接口。能夠在接口(或接口方法)上註解 @Cache* ,但只有當使用基於接口的代理時,才能使用它。Java 註解不是用接口繼承,若是使用基於類的代理( proxy-target-class="true")或基於代理的 weaving-based(mode="aspectj"),緩存設置沒法識別,對象也不會被包裝在緩存代理中。

在代理模式(默認狀況)中,只有經過代理進入的外部方法調用被截取。實際上,自調用目標對象中調用另外一個目標對象方法的方法在運行時不會致使實際的緩存,即便被調用的方法被 @Cacheable 標誌 - 能夠考慮 aspectj 模式。此外,代理必須被徹底初始化提供預期行爲,所以不該該依賴於初始化的代碼功能,即 @PostConstruct

32.3.7 使用自定義註解

自定義註解和 AspectJ
開箱即用,此功能僅使用基於代理的方法,但可以使用 AspectJ 進行一些額外的工做。

spring-aspects 模塊僅爲標準註解定義了一個切面。若是你定義了本身的註解,那還須要爲這些註解定義一個方面。檢查 AnnotationCacheAspect 爲例。

緩存抽象容許使用不一樣的註解識別什麼方法觸發緩存或者緩存回收。這是很是方便的模板機制,由於不須要重複緩存聲明(特別是在指定鍵和條件),或者在代碼庫不容許使用外部的導入(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註解,容器也能夠在運行時自動選擇其聲明並瞭解其含義。 請注意,如上所述,須要啓用註解驅動的行爲。

32.4 JCache (JSR-107) 註解

Spring Framework 4.1 以來,緩存抽象徹底支持 JCache 標準:即 @CacheResult@CachePut@CacheRemove@CacheRemoveAll 還有 @CacheDefaults@CacheKey@CacheValue 。這些註解被你們正確的使用,體現了緩存在 JSR-107 的實現:緩存抽象的內部實現,並提供了符合規範的默認 CacheResolverKeyGenerator 的實現。換句話說,若是你已經使用了 Spring 緩存抽象,那能夠平滑切換到這些標準註解,無需更改緩存存儲(或者配置)。

32.4.1 特徵總結

對於熟悉 Spring 緩存註解,下面描述了 Spring 註解和 JSR-107 對應的主要區別:

表 32.3. Spring vs JSR-107 緩存註解

Spring JSR-107 備註
@Cacheable @CacheResult 類似,@CacheResult 可緩存特定的異常,並強制執行該方法,無論緩存的結果。
@CachePut @CachePut Spring 調用方法以後獲取結果去更新緩存,但 JCache 將其做爲使用 @CacheValue 的參數傳遞。因此 JCache 容許實際方法調用以前或者以後更新緩存。
@CacheEvict @CacheRemove 類似, @CacheRemove 支持條件回收,以防止方法調用致使異常
@CacheEvict(allEntries=true) @CacheRemoveAll 查閱 @CacheRemove
@CacheConfig @CacheDefaults 容許相似的方式配置相同的屬性

JCache 的 javax.cache.annotation.CacheResolver 概念和 Spring 的 CacheResolver 是同樣的,除了 JCache 只支持單個緩存。默認下,根據註解聲明的名稱檢索要使用的緩存。若是沒有指定緩存的名稱,則會自動生成默認值。更多信息能夠查看 javadoc 的 @CacheResult#cacheName()

CacheResolverFactory 檢索出 CacheResolver 實例。每一個緩存操做均可以自定義工廠:

@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class)
public Book findBook(ISBN isbn)
注意
對於全部引用的類,Spring 會找到具體指定類型的 Bean。若是存在多個匹配,會建立一個新實例,並能夠正常的 Bean 生命週期回調(如依賴注入)

鍵由 javax.cache.annotation.CacheKeyGenerator 生成,和 Spring 的 KeyGenerator 能達到相同的目標。默認狀況下,全部方法參數都會被考慮,除非至少有一個參數被註解爲 @CacheKey。這和 Spring 的自定義鍵生成相似。例如,下面代碼操做是相同的,一個使用 Spring 抽象,另外一個使用 JCache:

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@CacheResult(cacheName="books")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed)

CacheKeyResolver 能夠在操做中指定,相似方式是 CacheResolverFactory。

JCache 管理註解方法拋出的異常:能夠防止緩存更新,但將異常做爲故障的標誌,而不是再次調用該方法。假設拋出 InvalidIsbnNotFoundException 異常,那麼 ISBN 的結構是無效的。若是這是一個永久的失敗,沒有任何書會被查詢出來。如下的緩存異常,以使具備無效的 ISBN 進一步直接拋出緩存的異常,而不是再次調用該方法。

@CacheResult(cacheName="books", exceptionCacheName="failures"
             cachedExceptions = InvalidIsbnNotFoundException.class)
public Book findBook(ISBN isbn)

32.4.2 啓用 JSR-107 支持

不須要特殊處理,去支持 JSR-107 和 Spring 的聲明式註解。JSR-17 API 和 spring-context-support 模塊在類路徑下,@EnableCachingcache:annotation-driven 二者會被啓用。

根據你的具體案例選擇須要的。你還可使用 JSR-107 API 和其餘使用 Spring 本身的註解來匹配服務。注意,若是這些服務影響相同的緩存,則使用一致的鍵生成實現。

32.5 緩存聲明式 XML 配置

若是不想使用註解,可使用 XML 進行聲明式配置緩存。因此不用註解方法的形式,而從外部指定目標方法和緩存指令(相似於聲明式事務管理)。之前的例子能夠轉化爲:

<!-- the service we want to make cacheable -->
<bean id="bookService" class="x.y.service.DefaultBookService"/>

<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
    <cache:caching cache="books">
        <cache:cacheable method="findBook" key="#isbn"/>
        <cache:cache-evict method="loadBooks" all-entries="true"/>
    </cache:caching>
</cache:advice>

<!-- apply the cacheable behavior to all BookService interfaces -->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
</aop:config>

<!-- cache manager definition omitted -->

上面的配置中,bookService 是可配緩存的服務。在 cache:advice 指定方法 findBooks

32.6 配置緩存存儲

開箱即用,緩存抽象提供了多種存儲集成。要使用它們,須要簡單地聲明一個適當的CacheManager - 一個控制和管理Caches,可用於檢索這些存儲。

32.6.1 JDK ConcurrentMap-based Cache

基於JDK的Cache實現位於org.springframework.cache.concurrent包下。它容許使用ConcurrentHashMap做爲後備緩存存儲。

<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
        </set>
    </property>
</bean>

上面的代碼片斷使用SimpleCacheManager建立一個CacheManager,爲兩個嵌套的ConcurrentMapCache實例命名爲defaultbooks。請注意,這些名稱是爲每一個緩存直接配置的。

因爲緩存是由應用程序建立的,所以它必須與其生命週期一致,使其適用於基本用例,測試或簡單應用程序。緩存規模好,速度快,但不提供任何管理、持久化能力或驅逐合同。

32.6.2 Ehcache-based Cache(基於Ehcache的緩存)

Ehcache 3.x徹底符合JSR-107標準,不須要專門的支持。

Ehcache 2.x的實現位於org.springframework.cache.ehcache包下。要使用它,只須要聲明適當的CacheManager

<bean id="cacheManager"
      class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>

<!-- EhCache library setup -->
<bean id="ehcache"
      class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>

此設置引導Spring IoC(經過ehcache bean)中的ehcache庫,而後將其鏈接到專用的CacheManager實現中。請注意,整個ehcache特定的配置是從ehcache.xml讀取的。

32.6.3 Caffeine Cache

Caffeine是Java 8的重寫Guava緩存,其實現位於org.springframework.cache.caffeine包下,並提供了對Caffeine的幾項功能的訪問。

根據須要,配置建立緩存的CacheManager很簡單:

<bean id="cacheManager"
      class="org.springframework.cache.caffeine.CaffeineCacheManager"/>

還能夠明確提供使用的緩存。在這種狀況下,只有manager才能提供:

<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
    <property name="caches">
        <set>
            <value>default</value>
            <value>books</value>
        </set>
    </property>
</bean>

Caffeine CacheManager也支持自定義的CaffeineCacheLoader。查看Caffeine documentation獲取更多信息。

32.6.4 GemFire-based Cache(基於GemFire的緩存)

GemFire是面向內存/磁盤支持,彈性可擴展,持續可用,主動(內置基於模式的訂閱通知),全局複製數據庫,並提供功能齊全的邊緣緩存。有關如何使用GemFire做爲CacheManager(及更多)的更多信息,請參考 Spring Data GemFire reference documentation

32.6.5 JSR-107 Cache

Spring的緩存抽象也可使用兼容JSR-107的緩存。JCache實現位於org.springframework.cache.jcache包下。
要使用它,只須要聲明適當的CacheManager

<bean id="cacheManager"
      class="org.springframework.cache.jcache.JCacheCacheManager"
      p:cache-manager-ref="jCacheManager"/>

<!-- JSR-107 cache manager setup  -->
<bean id="jCacheManager" .../>

32.6.6 Dealing with caches without a backing store(處理沒有後端存儲的緩存)

有時在切換環境或進行測試時,可能會有緩存聲明,而不配置實際的後備緩存。因爲這是一個無效的配置,所以在運行時將會拋出異常,由於緩存基礎結構沒法找到合適的存儲。在這種狀況下,而不是刪除緩存聲明(這能夠證實是乏味的),能夠鏈接一個不執行緩存的簡單的虛擬緩存,也就是強制每次執行緩存的方法:

<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
    <property name="cacheManagers">
        <list>
            <ref bean="jdkCache"/>
            <ref bean="gemfireCache"/>
        </list>
    </property>
    <property name="fallbackToNoOpCache" value="true"/>
</bean>

上面的CompositeCacheManager連接多個CacheManagers,另外,經過fallbackToNoOpCache標誌,添加了一個no op緩存,用於全部不被配置的緩存管理器處理的定義。也就是說,在jdkCachegemfireCache(上面配置)中找不到的每一個緩存定義都將由no op緩存來處理,這不會存儲任何致使目標方法每次執行的信息。

32.7 插入不一樣的後端緩存


顯然,有不少緩存產品能夠用做後備存儲。要插入它們,須要提供一個CacheManagerCache實現,由於不幸的是沒有可用的標準供咱們使用。這聽起來比實際上更難聽,這些類每每是簡單的適配器,將緩存抽象框架映射到存儲API的頂部,就像ehcache類能夠顯示同樣。大多數CacheManager類均可以使用org.springframework.cache.support包中的類,例如AbstractCacheManager,其中只須要完成實際的映射便可完成代碼。咱們但願及時提供與Spring集成的庫能夠填補這個小的配置差距。

32.8 如何設置 TTL/TTI/Eviction policy/XXX 功能?


直接經過您的緩存提供商。緩存抽象是一個抽象而不是緩存實現。您正在使用的解決方案可能支持各類數據策略和其餘解決方案不一樣的拓撲(例如JDK ConcurrentHashMap),由於緩存中的提取將無濟於事,由於不須要後臺支持。這樣的功能應該經過後臺緩存,配置它或經過其本機API直接控制。

文末福利

Java 資料大全 連接:https://pan.baidu.com/s/1pUCC... 密碼:b2xc
更多資料: 2020 年 精選阿里 Java、架構、微服務精選資料等,加 v ❤ :qwerdd111

本文由博客一文多發平臺 OpenWrite 發佈!
相關文章
相關標籤/搜索