緩存抽象

緩存抽象

 自版本3.1以來,Spring框架爲透明地向現有的Spring應用程序添加緩存提供了支持。與事務支持類似,緩存抽象允許一致地使用各種緩存解決方案,對代碼的影響最小。

從Spring4.1開始,緩存抽象在JSR-107註釋和更多定製選項的支持下得到了顯著的擴展。

 

理解緩存抽象

  緩存與緩衝區

術語“緩衝區”和“緩存”傾向於互換使用。不過,請注意,它們代表不同的東西。傳統上,緩衝區用作快速實體和慢速實體之間數據的中間臨時存儲。由於一方必須等待另一方(這會影響性能),緩衝區通過允許整個數據塊同時移動而不是以小塊移動來緩解這一問題。數據只從緩衝區寫入和讀取一次。此外,緩衝區對至少一個知道它的方可見。

另一方面,根據定義,緩存是隱藏的,而且任何一方都不知道緩存的發生。它還可以提高性能,但可以讓同一數據以快速的方式多次讀取。

緩存抽象的核心是將緩存應用於Java方法,從而根據緩存中可用的信息減少執行次數。也就是說,每次調用目標方法時,抽象都會應用一個緩存行爲,檢查是否已經爲給定參數執行了該方法如果已執行,則返回緩存結果,而不必執行實際方法如果方法尚未執行,則執行該方法,並將結果緩存並返回給用戶,以便下次調用該方法時返回緩存的結果。這樣,對於給定的參數集和重用的結果,昂貴的方法(無論是CPU綁定的還是IO綁定的)只能執行一次,而不必再次實際執行該方法。緩存邏輯的應用是透明的,不會對調用程序造成任何干擾。

此方法僅適用於保證爲給定輸入(或參數)返回相同輸出(結果)的方法,無論執行多少次。

緩存抽象提供了其他與緩存相關的操作,例如更新緩存內容或刪除一個或所有條目的能力。如果緩存處理在應用程序過程中可能更改的數據,則這些功能非常有用。

   與spring框架中的其他服務一樣,緩存服務是一個抽象(不是緩存實現),需要使用實際存儲來存儲緩存數據,也就是說,抽象使您不必編寫緩存邏輯,但不提供實際的數據存儲。

  這個抽象由org.springframework.cache.Cache和org.springframework.cache.CacheManager接口具體化。

spring提供了這種抽象的一些實現:基於jdk java.util.concurrent.ConcurrentMap的緩存、ehcache 2.x、gemfire緩存、 Caffeine和jsr-107兼容的緩存(例如ehcache 3.x)。有關插入其他緩存存儲和提供程序的詳細信息,請參閱插入不同的後端緩存。

緩存抽象對多線程和多進程環境沒有特殊處理,因爲這些特性由緩存實現處理。

如果有多進程環境(即部署在多個節點上的應用程序),則需要相應地配置緩存提供程序。根據您的用例,在多個節點上覆制相同的數據就足夠了。但是,如果在應用程序過程中更改數據,則可能需要啓用其他傳播機制。

緩存一個特定的項與典型的get if not found then-proceed then put finally代碼塊是直接等價的,後者是通過編程緩存交互找到的沒有應用鎖,多個線程可能會嘗試同時加載同一項這同樣適用於驅逐。如果多個線程同時嘗試更新或收回數據,則可能會使用過時的數據。某些緩存提供程序在該領域提供高級功能。有關詳細信息,請參閱緩存提供程序的文檔。

要使用緩存抽象,需要注意兩個方面:

Caching declaration: Identify the methods that need to be cached and their policy.

Cache configuration: The backing cache where the data is stored and from which it is read.

緩存聲明:標識需要緩存的方法及其策略。

緩存配置:背後的緩存存儲數據的地方從中讀取數據。

 

基於聲明性註釋的緩存

對於緩存聲明,spring的緩存抽象提供了一組java註釋:

@Cacheable

觸發緩存填充

@CacheEvict

觸發緩存收回。

@CachePut

在不干擾方法執行的情況下更新緩存。

@Caching

重新組合要應用於方法的多個緩存操作。

@CacheConfig

在類級別共享一些與緩存相關的公共設置。

 

@Cacheable 註釋

顧名思義,可以使用@Cacheable來劃分可緩存的方法,即結果存儲在緩存中的方法,以便在隨後的調用(使用相同的參數)中返回緩存中的值,而不必實際執行該方法。緩存中的值返回時不必實際執行該方法在最簡單的形式中,註釋聲明需要與註釋方法關聯的緩存的名稱,如下例所示:

@Cacheable("books")

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

 

在前面的代碼片段中,findbook方法與名爲books的緩存相關聯。每次調用該方法時,都會檢查緩存以查看調用是否已執行且不必重複在大多數情況下,只聲明一個緩存,而註釋允許指定多個名稱,以便使用多個緩存。在這種情況下,在執行方法之前檢查每個緩存。如果至少命中一個緩存,則返回關聯的值。

 不包含該值的所有其他緩存也將更新,即使緩存的方法實際上沒有執行。

@Cacheable({"books", "isbns"})

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

 

默認Key生成

由於緩存本質上是鍵值存儲,所以對緩存方法的每次調用都需要轉換爲適合緩存訪問的鍵。緩存抽象使用基於以下算法的簡單Key生成器:

  如果沒有給出參數,則返回SimpleKey.EMPTY。

  如果只給定一個參數,則返回該實例。

  如果給定了多個參數,則返回包含所有參數的SimpleKey。

只要參數具有自然鍵並實現有效的 hashCode()  equals()方法,這種方法對大多數用例都很有效。如果不是這樣,你需要改變策略。要提供不同的默認密鑰生成器,需要實現org.springframework.cache.interceptor.KeyGenerator接口。

隨着Spring4.0的發佈,默認的key生成策略發生了變化早期版本的Spring使用了一種key生成策略,對於多個key參數,只考慮參數的hashCode()而不考慮equals()這可能會導致意外的key衝突(有關背景信息,請參閱SPR-10237)新的simplekeygenerator在這種情況下使用複合鍵。

如果希望繼續使用以前的key策略,可以配置不推薦使用的org.springframework.cache.interceptor.defaultkeygenerator類,或創建自定義的基於hashcode的keygenerator實現。

 

自定義key生成聲明

  由於緩存是通用的,因此目標方法很可能具有各種簽名,這些簽名無法輕鬆映射到緩存結構的頂部。

當目標方法有多個參數,其中只有一些參數適合緩存時(而其他參數僅由方法邏輯使用),這一點就變得很明顯。請考慮以下示例:

@Cacheable("books")

public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

乍一看,雖然這兩個布爾參數會影響找到書的方式,但它們對緩存沒有用處此外,如果只有一個是重要的,而另一個不是呢?

對於這種情況,@Cacheable註釋允許您指定如何通過key屬性生成key。您可以使用spel來選擇感興趣的參數(或它們的嵌套屬性)、執行操作,甚至調用任意方法,而無需編寫任何代碼或實現任何接口。與默認生成器相比,這是推薦的方法,因爲隨着代碼基的增長,方法在簽名方面往往會有很大的不同。雖然默認策略可能適用於某些方法,但很少適用於所有方法。

下面的示例介紹了各種spel聲明(如果您不熟悉spel,請自己閱讀spring表達式語言):

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

 

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

如果負責生成key的算法過於特定或需要共享key,則可以在操作上定義自定義密鑰生成器爲此,請指定要使用的KeyGenerator bean實現的名稱,如下例所示:

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")

public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

key和keygenerator參數是互斥的,指定這兩個參數的操作將導致異常。

 

Default Cache Resolution

緩存抽象使用一個簡單的CacheResolver,它通過使用配置的CacheManager檢索在操作級別定義的緩存。要提供不同的默認緩存解析器,需要實現

org.springframework.cache.interceptor.CacheResolver接口。

自定義緩存解析

默認的緩存解析非常適合使用單個緩存管理器且沒有複雜緩存解析要求的應用程序。

對於使用多個緩存管理器的應用程序,可以將緩存管理器設置爲用於每個操作,如下例所示:

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")

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

指定anotherCacheManager 緩存管理器

您還可以完全以類似於替換Key生成的方式替換CacheResolver爲每個緩存操作請求解析,讓實現根據運行時參數實際解析要使用的緩存下面的示例演示如何指定緩存解析程序:

@Cacheable(cacheResolver="runtimeCacheResolver")

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

指定一個runtimeCacheResolver緩存解析器。

由於Spring 4.1,緩存註釋的value屬性不再是必需的,因爲不管註釋的內容如何,緩存解析程序都可以提供這個特定的信息。

與key和keyGenerator類似,cacheManager和cacheResolver參數是互斥的,指定這兩個的操作將導致異常因爲cacheresolver實現忽略了自定義cachemanager。這可能不是你所期望的。

 

同步緩存

在多線程環境中,可以爲同一個參數併發調用某些操作(通常在啓動時)。默認情況下,緩存抽象不會鎖定任何內容,並且可以多次計算相同的值,從而破壞了緩存的目的

對於這些特殊情況,可以使用sync屬性指示基礎緩存提供程序在計算值時鎖定緩存項結果,只有一個線程正忙於計算該值,而其他線程則被阻塞,直到緩存中的條目更新下面的示例演示如何使用sync屬性:

@Cacheable(cacheNames="foos", sync=true)

public Foo executeExpensiveOperation(String id) {...}

這是一個可選功能,您喜愛的緩存庫可能不支持它。核心框架提供的所有CacheManager實現都支持它有關詳細信息,請參閱緩存提供程序的文檔。

條件緩存

有時,方法可能不適合一直進行緩存(例如,它可能依賴於給定的參數)。緩存註釋通過condition參數支持這樣的功能,condition參數接受一個SpEL表達式,該表達式的計算結果爲true或false如果爲true,則緩存該方法。如果不是,它的行爲就好像方法沒有被緩存一樣(也就是說,無論緩存中有什麼值或使用了什麼參數,每次都會執行該方法)。

僅當參數名的長度小於32時,才緩存以下方法:

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

public Book findBook(String name)

設置條件 @Cacheable

除了condition參數之外,您還可以使用除非參數否決向緩存中添加值。與條件不同,除非在調用方法後計算表達式。爲了在前面的示例上進行擴展,我們可能只希望緩存平裝書(paperback books),如下例所示:

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

public Book findBook(String name)

使用“除非”屬性阻止精裝本(hardbacks)進行緩存。

 

所述緩存抽象介質java.util.Optional,使用其內容作爲所述緩存值只有在存在時才使用。result總是引用業務實體,而不是受支持的包裝器,因此前面的示例可以重寫如下:

@Cacheable(cacheNames="book",condition="#name.length()<32", unless="#result?.hardback")

public Optional<Book> findBook(String name)

注意,結果仍然是指書本Book而不是可選Optional的因爲它可能是空的,我們應該使用安全導航操作符。

 

Available Caching SpEL Evaluation Context

可用緩存spel求值上下文

每個SpEL表達式都根據一個專用上下文進行求值。除了內置參數外,框架還提供專用的緩存相關元數據,如參數名。

下表描述了上下文可用的項,以便您可以將它們用於鍵和條件計算:

緩存spel可用元數據名稱

Name

Location

Description

Example

methodName

Root object

The name of the method being invoked

正在調用的方法的名稱。

#root.methodName

method

Root object

The method being invoked

正在調用的方法。

#root.method.name

target

Root object

The target object being invoked

正在調用的目標對象。

#root.target

targetClass

Root object

被調用的目標的類

#root.targetClass

args

Root object

The arguments (as array) used for invoking the target

用於調用目標的參數(作爲數組)

#root.args[0]

caches

Root object

Collection of caches against which the current method is executed

對其執行當前方法的緩存的集合

#root.caches[0].name

Argument name

Evaluation context

Name of any of the method arguments.

 If the names are not available (perhaps due to having no debug information),the argument names are also available under the #a<#arg>where #arg 

stands for the argument index (starting from 0).

任何方法參數的名稱。如果名稱不可用(可能是由於沒有調試信息),則參數名稱也可以在#a<#arg>下使用,其中#arg表示參數索引(從0開始)。

#iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias).

result

Evaluation context

The result of the method call (the value to be cached). Only available in unless expressions, cache put expressions (to compute the key), or cache evict expressions (when beforeInvocation is false). For supported wrappers (such as Optional), #result refers to the actual object, not the wrapper.方法調用的結果(要緩存的值)僅在除非表達式、緩存放置表達式(用於計算鍵)或緩存收回表達式(當beforeInvocation爲false時)中可用對於支持的包裝器(例如可選的),#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要求指定受操作影響的一個或多個緩存,允許指定自定義緩存和key解析或條件,並具有一個額外的參數(allentries),該參數指示是否需要執行緩存範圍的逐出,而不僅僅是逐出項(基於key)。

 以下示例將從圖書緩存中逐出所有條目:

@CacheEvict(cacheNames="books", allEntries=true)

public void loadBooks(InputStream batch)

使用all entries屬性從緩存中逐出所有條目。當需要清除整個緩存區域時,此選項就會派上用場。而不是逐出每個條目(這將花費很長的時間,因爲它是低效的),所有的條目是在一個操作中刪除,如前面的例子所示。注意,框架忽略了在此場景中指定的任何鍵,因爲它不適用(整個緩存被逐出,而不僅僅是一個條目)。

還可以指示在(默認值)之後還是在使用“beforeInvocation ”屬性執行該方法之前是否應該進行驅逐。前者提供與其餘註釋相同的語義:一旦該方法成功完成,則執行高速緩存上的動作(在這種情況下,驅逐)。如果該方法不執行(因爲它可能被緩存)或者拋出異常,則不會發生驅逐。後者(beforeInvocation =true)導致驅逐總是在調用方法之前發生。在不需要將驅逐與方法結果掛鉤的情況下,這是有用的。

注意,void方法可以與@CacheEvict一起使用-由於這些方法充當觸發器,返回值將被忽略(因爲它們不與緩存交互)。@cacheable的情況並非如此,它向緩存中添加或更新數據,因此需要結果。

 

@Caching註釋

有時,需要指定相同類型的多個註釋(例如@CacheEvict或@CachePut),因爲不同緩存之間的條件或鍵表達式不同。@緩存允許在同一方法上使用多個嵌套的@Cacheable、@CachePut和@CacheEvict註釋。下面的示例使用兩個@CacheEvict註釋:

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })

public Book importBooks(String deposit, Date date)

 @CacheConfig註釋

迄今爲止,我們已經看到緩存操作提供了許多定製選項,並且可以爲每個操作設置這些選項。但是,某些自定義選項在應用到類的所有操作時可能是繁瑣的。例如,指定要用於類的每個緩存操作的緩存的名稱可以由單個級別定義替代。這是@CacheConfig開始播放的位置。

@CacheConfig("books")

public class BookRepositoryImpl implements BookRepository

 {

@Cacheable

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

}

 

使用@CacheConfig設置緩存的名稱。

cacheConfig是允許共享緩存名稱、自定義密鑰生成器、自定義CacheManager和自定義CacheResolver的類級別註釋。將此註釋放置在類上不會啓用任何緩存操作。

操作級定製總是覆蓋@CacheConfig上的自定義集。因此,這爲每個緩存操作提供了三個級別的自定義:

全局配置,可用於CacheManager、KeyGenerator。

在類級別,使用@CacheConfig。

在操作級別。

 

啓用緩存註釋

需要注意的是,儘管聲明緩存註釋不會自動觸發它們的操作--就像Spring中的許多東西一樣,這個特性必須以聲明方式啓用(這意味着如果您懷疑緩存是罪魁禍首,您可以只刪除一個配置行而不是代碼中的所有註釋來禁用它)。

要啓用緩存註釋,請將註釋@EnableCache添加到您的@Configuration類中的一個:

@Configuration

@EnableCaching 

public class AppConfig { }

Alternatively, for XML configuration you can use the cache:annotation-driven element:

<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將緩存行爲添加到應用程序的方式。該配置有意與@Transaction的配置相似。

 

用於處理緩存註釋的默認通知模式是代理,它允許僅通過代理截獲調用。同一類中的本地呼叫不能以這種方式被截獲。對於更高級的攔截模式,請考慮切換到AspectJ模式,並結合編譯時或加載時編織。

有關實現CachingConfigrer所需的高級自定義(使用Java配置)的更多細節,請參見javadoc。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章