Spring cache簡單使用guava cache


Spring cache簡單使用

前言

spring有一套和各種緩存的集成方式。類似於sl4j,你可以選擇log框架實現,也一樣可以實現緩存實現,比如ehcache,guava cache.

什么時候用緩存

首先,緩存是為了省略消耗時間的步驟,比如io。當我需要從數據庫查詢的數據幾乎沒有變化,或者變化很少的時候,我就沒必要每次都去數據庫里拿數據了。大可以放到本地,直接取出來就可以了。這時候需要注意的是數據一致性問題,緩存的數據是否被更改了,數據是否有效。

我的項目是分布式部署的,但還沒有搭建分布式緩存服務。我采用的本地緩存,也就是說,我的緩存只能在本實例中,跨機器訪問則不命中。即便如此也大大減少了訪問數據庫的開銷了。

配置緩存

這里采用guava cache作為本地緩存。將guava cache注冊到cacheManger里就可以調用了。

1.配置cacheManger

首先針對要緩存的類型,配置緩存策略。這里設置最大緩存數量和緩存過期時間

public static final String HOTEL_POSTION = "hotel_position";  //cache key
@Value("${cache.guavaCache.hotelPosition.maxSize}")
private long hotelPositionMaxSize;
@Value("${cache.guavaCache.hotelPosition.duration}")
private long hotelPositionDuration;

private GuavaCache buildHotelPositionCache() {
        return new GuavaCache(HOTEL_POSTION,
                CacheBuilder.newBuilder()
                        .recordStats()
                        .maximumSize(hotelPositionMaxSize)
                        .expireAfterWrite(hotelPositionDuration, TimeUnit.DAYS)
                        .build());
}

將剛才創建的緩存策略添加到cacheManger:

	@Bean
    public CacheManager cacheManager() {
        SimpleCacheManager manager = new SimpleCacheManager();
        List list = new ArrayList();
        list.add(buildHotelPositionCache());
        manager.setCaches(  list );

        return manager;
    }
2.配置要緩存的方法

在需要使用這個緩存的地方,增加一行注解

@Cacheable(value = CacheManagementConfig.HOTEL_POSTION, key = "{#hotelId}", condition = "", unless = "!#result.isSuccessful()")
	public BaseDomainResponse<HotelPosition> getHotelPosition(int hotelId, String apiToken) {
    	//......
    }
  • @Cacheable表示這個方法要被緩存
  • value string,表示這個方法緩存的唯一性標識,即這方法緩存的key。語法為SpEL.
  • key String,表示每條請求緩存的key,即如果key相同,則返回緩存中對應的數據
  • condition boolean,可以額外添加緩存的條件.語法為SpEL.
  • unless boolean, 配置哪些條件下的記錄不緩存。語法為SpEL.
  • result表示return的這個對象,可以同result來調用這個對象的屬性,比如isSuccessful()就是我返回對象的一個方法。





官方文檔

此處學習官方文檔cache部分,spring版本4.1+。

At its core, the abstraction applies caching to Java methods, reducing thus the number of executions based on the information available in the cache. That is, each time a targeted method is invoked, the abstraction will apply a caching behavior checking whether the method has been already executed for the given arguments. If it has, then the cached result is returned without having to execute the actual method; if it has not, then method is executed, the result cached and returned to the user so that, the next time the method is invoked, the cached result is returned. This way, expensive methods (whether CPU or IO bound) can be executed only once for a given set of parameters and the result reused without having to actually execute the method again. The caching logic is applied transparently without any interference to the invoker.

這個緩存應用於java 方法級別緩存,通過緩存中的數據來減少方法執行次數。每當目標方法被調用,spring cache會執行一個緩存行為來檢查這個相同參數的方法是否已經被執行。如果被執行過了,那么不執行方法直接返回緩存中的結果。 通過這樣,代價高的方法(CPU或IO依賴)可以只執行一次,相同參數的結果會復用而不是真正的執行這個方法。這個緩存邏輯對調用者來說是透明的,也就是調用者不用管這個緩存邏輯。

Just like other services in the Spring Framework, the caching service is an abstraction (not a cache implementation) and requires the use of an actual storage to store the cache data - that is, the abstraction frees the developer from having to write the caching logic but does not provide the actual stores. This abstraction is materialized by the org.springframework.cache.Cache and org.springframework.cache.CacheManager interfaces.

There are a few implementations of that abstraction available out of the box: JDK java.util.concurrent.ConcurrentMap based caches, Ehcache 2.x, Gemfire cache, Caffeine, Guava caches and JSR-107 compliant caches (e.g. Ehcache 3.x). See Section 36.7, “Plugging-in different back-end caches” for more information on plugging in other cache stores/providers.

spring cache是一個抽象的概念,沒有提供實現方式去存儲數據,開發者可以自己選擇任意的實現。比如DK java.util.concurrent.ConcurrentMap based caches, Ehcache 2.x, Gemfire cache, Caffeine, Guava caches and JSR-107 compliant caches (e.g. Ehcache 3.x)。

If you have a multi-process environment (i.e. an application deployed on several nodes), you will need to configure your cache provider accordingly. Depending on your use cases, a copy of the same data on several nodes may be enough but if you change the data during the course of the application, you may need to enable other propagation mechanisms.

Caching a particular item is a direct equivalent of the typical get-if-not-found-then- proceed-and-put-eventually code blocks found with programmatic cache interaction: no locks are applied and several threads may try to load the same item concurrently. The same applies to eviction: if several threads are trying to update or evict data concurrently, you may use stale data. Certain cache providers offer advanced features in that area, refer to the documentation of the cache provider that you are using for more details.

To use the cache abstraction, the developer needs to take care of two aspects:

  • caching declaration - identify the methods that need to be cached and their policy
  • cache configuration - the backing cache where the data is stored and read from

如果你采用多過程環境(比如,一個項目部署到多個服務節點,即分布式部署),你需要配置相應的的緩存實現。在你的使用案例中,同樣數據的拷貝已經足夠使用了。但如果你在這期間修改了數據,你需要使用其他傳播機制來控制緩存的一致性。
緩存一個指定的條目直接等價於獲取-如果-不存在-然后-執行-並且-最好放入緩存的程序邏輯的代碼塊:不會阻塞並且多線程可以並發地加載相同的條目。緩存更新策略也一樣:如果幾個縣城嘗試並發地更新或者移除緩存的數據,你需要使用過期的數據。在這個領域,特定的緩存實現提供更先進的方式,參考你使用的緩存實現的文檔來獲取等多的詳情。
想要使用這個抽象的緩存,開發者需要關心兩個方面:

  • 緩存聲明 - 定義需要被緩存的方法以及對應的緩存策略。
  • 緩存配置 - 數據存儲和讀取的實現。

1.基於注解的聲明式緩存

緩存抽象提供了一系列的java注解:

  • @Cacheable 觸發緩存邏輯
  • @CacheEvict 觸發緩存逐出邏輯
  • @CachePut 不干涉方法執行地更新緩存
  • @Caching 重組一個方法上的多重緩存操作

1.1@Cacheable 注解

就像名字所暗示的,@Cacheable是用來區分方法是否可緩存的。也就是說,哪個方法可以把結果存儲到cache中,所以隨后調用(相同的參數)時會返回cache中的值,而且並不會實際上運行這個method。最簡單的用法:注解需要一個cache的name來關聯這個method。

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

在上述的片段中,method findBook關聯到名字叫做books的cache。每次這個方法被調用的時候,cache會檢查這個調用是否已經被執行過了並且不必重復執行。大多數情況下,只聲明一個cache,但這個注解支持聲明多個name,因此可以使用多個cahce。這樣,在執行method之前每個cache都會檢查是否存在 - 如果至少一個cache命中了,然后就會返回關聯的值。

默認key注冊模式

因為cache本質上是key-value存儲,每次調用緩存的method需要被翻譯成一個合適的key來獲取緩存。Out of the box(這句話不知道該怎么翻譯,box應該是指這個類,即在這個method所在的類之外), 緩存代理(cache abstraction)使用一個基於以下算法的簡單的KeyGenerator

  • 如果沒有參數,key就是SimpleKey.EMPTY.
  • 如果只有一個參數,則返回那個參數.
  • 如果多個參數,返回SimpleKey包含所有的參數

只要參數有__natural keys__ 並且實現了合法的hashCode()equals(),這個方法適合於大多數使用案例。如果不是,則key產生策略就需要改變。

不想使用默認的key生產機制,你需要實現接口:org.springframework.cache.interceptor.KeyGenerator.



自定義Key產生聲明

因為caching是普遍的,所以很可能目標method有各種簽名(signatures)不可以簡單的映射到cache結構中。這個在目標mothod有多個參數但只有部分參數和緩存關聯的時候就變得明顯。
簡單的說,cache默認把參數組合成一個key,這個key對應一個結果,下載遇到相同參數就會對應這個key,可以去除這個key對應的結果。然而,有時候,我們有多個參數,比如a,b,c。只有a和b和緩存的結果有關,c是變化的。

@Cacheable("books")
public Book findBook(ISBN a, String b, String token)

假設我這個findBook需要一個token來獲取權限,但和book無關。那么我們遇到相同的a就可以返回對應的book了,不需要關心token。換句話說,自己可以定義緩存的條件,只要a和b相同,則命中同一個緩存。
然而,默認的會將a和b還有token組成一個key,只有這個三個相同的時候才會命中緩存。這時候就需要我們自定義key的組成了。

以下示例各種SpEL聲明,通過SpEL語法來聲明key:

//僅僅使用key(isbn)
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, String b, String token)

//使用isbn的一個屬性當做key
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

//調用某個類的某個方法來生成key
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

//組合key(a和b)
@Cacheable(cacheNames="books", key="#a.concat(#b)")
public Book findBook(String a, String b, String token)

上述代碼片段顯示了選擇一個特定的參數或者一個參數的屬性或者任意的方法或者組合參數作為key是多么簡單。

如果產生key的算法太特殊或者如果這個key需要共享,你可以自定義一個keyGenerator。只要聲明自定義的KeyGenerator的bean實現就可以了:

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
`key`和`keyGenerator`是互斥的,如果同時聲明會拋出異常。



默認的Cache Resolution

Out of the box, 緩存代理使用簡單的CacheResolver來獲取cache, 這個是可以使用CacheManager來手動配置的。
如果不想使用默認的cache resolver,你需要實現接口:org.springframework.cache.interceptor.CacheResolver

自定義Cache Resolution

默認的cache resolution適合於使用一個CacheManager並且沒有復雜的cache resolution.

對於采用多個cache managers的應用,要設置cacheManger

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

當然也可以完全替換CacheResolver, 就像key generation一樣簡單。每次cache操作都會請求這個resolution,基於運行時的參數來交給它的實現。

@Cacheable(cacheResolver="runtimeCacheResolver")
public Book findBook(ISBN isbn) {...}
就像`key`和`keyGenerator`一樣,`cacheManager`和`cacheResolver`參數也是互斥的,同時聲明會拋出異常。



同步caching

在多線程環境,一個操作也許會並發的執行(比如啟動的時候)。 默認的,cache代理不會lock並且同樣的數據也許會計算多次,這與cache的目標相悖。

在這些特殊的場景,當計算的時候,參數sync可以用來通知將cache lock cache entry. 這樣,只有一個線程可以計算,其他的等待entry被更新到cache。

@Cacheable(cacheNames="foos", sync="true")
public Foo executeExpensiveOperation(String id) {...}



條件緩存(conditional caching)

有時候,一個method也許並不適合全部緩存(比如,根據參數緩存)。cache注解通過參數condition來支持這種功能,同樣使用SpEL表達式,結果為true或false, 如果是true則緩存,否則表現為這個method沒有緩存。這個判斷會在每次獲取value的時候執行,無論緩存的value是什么以及無論使用哪個參數。

一個簡單的示例,一下method只有在參數name長度小於32的時候執行緩存。

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

除了使用condition, unless可以用來否決把結果加入緩存。不同的是,unless的表達式會在method執行結束后考量,就是mehtod執行完后判斷是否加入緩存。擴展之前的示例 -- 我們只需要緩存paperback books. unless為true的時候不緩存。

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

這里#result就是指向返回值。



可使用的SpEL表達式

每個SpEL表達式都有一個專門的context。除了采用參數構建表達式,框架提供了專門的與caching相關的元數據,比如參數名。下表列出了在context中可用的參數,你可以用來當做key和conditional 處理。

Name Location Description Example
methodName root object 被執行的method的名字 #root.methodName
method root object 被執行的method #root.method.name
target root object 執行的對象 #root.target
targetClass root object 執行對象的class #root.targetClass
args root object 執行對象的參數們(數組) #root.args[0]
caches root object 當前method對應的緩存集合 #root.caches[0].name
argument name evaluation context 任意method的參數。如果特殊情況下參數還沒有被賦值(e.g. 沒有debug信息),參數可以使用#a<#arg>來表示,其中#arg代表參數順序,從0開始 #iban或者#a0(也可以使用#p0或者#p<#arg>注解來啟用別名)
result evaluation context method執行的結果(要緩存的對象),僅僅在unless表達式中可以使用,或者cache put(用來計算key),或者cache evict表達式(當beforeInvocation=false). 為了支持wrapper,比如Optional#result指向世紀的對象,不是wrapper. #result
參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM