Spring Cache 帶你飛
接着上一篇講了 Spring Cache 如何被 Spring Aop 代理加載對應的代碼,以及何如注入相關界面邏輯。
Spring Cache 帶你飛(一)
本篇我們圍繞兩個要點展開:
一個資料是如何被Spring Cache 放入緩存的。
Spring Cache 如何擴充存儲源,即支援不同的緩存技術。
Spring Cache 相關的注解有 5 個:
@Cacheable 在調用方法的同時能夠根據方法的請求參數對結果進行緩存。
@CachePut 調用發放的同時進行 Cache 存儲,作用于方法上。
@CacheEvict 删除,作用于方法上。
@Caching 用于處理複雜的緩存情況,一次性設定多個緩存,作用于方法上。
@CacheConfig 可以在類級别上标注一些公用的緩存屬性,所有方法共享。
@Cacheable 是我們最常使用的注解:
cacheNames 和 value 這兩個屬性任意使用一個都可以,它們的作用可以了解為 key 的字首。
key 和 keyGenerator 是互斥的一對。當指定了 key 的時候就會使用你指定的 key + 參數 作為緩存 key。否則則使用預設 keyGenerator(SimpleKeyGenerator)或者你自定義的 Generator 來生成 key。
預設的 SimpleKeyGenerator 通過源碼我們能看到它的生成規則:
如果方法沒有入參則抛異常,即必須要有入參才能建構 key;
如果隻有一個入參,則使用該入參作為 key=入參值。
如果有多個入參則傳回包含所有入參的構造函數 <code>new SimpleKey(params)</code>。
Spring 官方推薦使用顯式指定 key 的方式來生成 key。當然你也可以通過自定義 KeyGenerator 來實作自己制定規則的 key 生成方式,隻需要實作 KeyGenerator 接口即可。
注意 key 屬性為 spEL 表達式,如果要寫字元串需要将該字元串用單引号括起來。比如我們有如下配置:
假設 <code>name = xiaoming</code>,那麼緩存的 <code>key = userInfo::p_xiaoming</code>。
condition 參數的作用是限定存儲條件:
上例限制條件為 <code>sex == 1</code> 的時候才寫入緩存,否則不走緩存。
unless 參數跟 condition 參數相反,作用是當不滿足某個條件的時候才寫入緩存。
sync 字段上一篇說過,多線程情況下并發更新的情況是否隻需要一個線程更新即可。
還有個屬性 cacheManager 比較大頭放在後面單獨說,從命名上能看出它是 cache 的管理者,即指定目前 Cache 使用何種 Cache 配置,比如是 Redis 還是 local Cache 等等。這也是我們這一篇要讨論的重點。
CacheConfig 注解包含以下配置:
如果你在一個類中使用多個 Cache 注解,并且這些 Cache 注解有公共的基礎操作,比如:使用相同的 Cache key 生成規則,使用相同的 Cache Name 字首等等,那麼你就可以定義一個 CacheConfig 來統一單獨管理這些 Cache 操作。
上面示例中的 兩個 Cache Key 都會有一個公共字首 ”user“。需要注意的是:CacheConfig 注解的優先級高于同類當中别的注解,如果你在 CacheConfig 中配置了 cacheNames,方法中也配置了,那麼 CacheConfig 中的 cacheNames 會覆寫掉方法上的配置。
@Caching 注解适用于複雜緩存操作的場景,當你有多個緩存操作的需求,比如下例:你需要先删除就緩存,再插入新資料到緩存:
那麼你可以使用 @Caching 注解來操作多個緩存。
注解的使用就說到這裡,其餘幾個注解的配置基本同 @Cacheable 差不多,剩下的大家可以自己學習。接下來我們要說的重點來了:待緩存的資料到底是如何被存儲起來的。Spring Cache 如何知道目前要使用的資料源。
Spring EL 對 Cache 的支援
Name
Location
Description
Example
methodName
Root object
被調用的方法的名稱
<code>#root.methodName</code>
method
被調用的方法
<code>#root.method.name</code>
target
目前調用方法的對象
<code>#root.target</code>
targetClass
目前調用方法的類
<code>#root.targetClass</code>
args
目前方法的參數
<code>#root.args[0]</code>
caches
目前方法的緩存集合
<code>#root.caches[0].name</code>
Argument name
Evaluation context
目前方法的參數名稱
<code>#iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias).</code>
result
方法傳回的結果(要緩存的值)。隻有在 <code>unless 、@CachePut(用于計算鍵)或@CacheEvict(beforeInvocation=false)</code>中才可用.對于支援的包裝器(例如Optional),<code>#result</code>引用的是實際對象,而不是包裝器
<code>#result</code>
Spring 在 application.yml 中提供配置檔案支援,通過配置 <code>spring.cache.type</code> 标簽來指定目前要使用的存儲方案,目前支援的有:
使用的時候需要引入相關存儲對應的 jar 包以及相關的配置。
Java Caching 定義了 5 個核心接口,分别是 CachingProvider, CacheManager, Cache, Entry和 Expiry
CachingProvider 用于配置和管理 CacheManager,目前它隻有一個唯一的實作類 EhcacheCachingProvider,ehcache 也是 Spring 預設提供的實作之一。其餘的第三方緩存元件都沒有用到。
CacheManager 定義了建立、配置、擷取、管理和控制多個唯一命名的 Cache,這些 Cache 存在于 CacheManager 的上下文中。一個 CacheManager 僅被一個 CachingProvider 所擁有。
Cache 是一個類似 Map 的資料結構并臨時存儲以 Key 為索引的值。一個 Cache 僅被一個 CacheManager 所擁有。
Entry是一個存儲在 Cache 中的 key-value 對。
Expiry 每一個存儲在 Cache 中的條目有一個定義的有效期。一旦超過這個時間,條目為過期的狀态。一旦過期,條目将不可通路、更新和删除。緩存有效期可以通過 ExpiryPolicy 設定。

Spring 定義了<code>org.springframework.cache.CacheManager</code>和<code>org.springframework.cache.Cache</code>接口來統一不同的緩存技術。其中,CacheManager 是 Spring 提供的各種緩存技術抽象接口,Cache 接口包含了緩存的各種操作。
針對不同的緩存方案需要提供不同的 CacheManager,Spring提供的實作類包括:
SimpleCacheManager:使用檢點的 Collection 來存儲緩存,主要用來測試
ConcurrentMapCacheManager:使用 ConcurrentMap 來存儲緩存
NoOpCacheManager:僅測試用途,不會實際存儲緩存
EhCacheManager:使用 EhCache 作為緩存技術
GuavaCacheManager:使用 Google Guava 的 GuavaCache 作為緩存技術
HazelcastCacheManager:使用 Hazelcast 作為緩存技術
JCacheManager:支援 JCache(JSR—107)标準的實作作為緩存技術
RedisCacheManager:使用 Redis 作為緩存技術
CacheManager 的加載來自于 <code>spring.factories</code> 檔案中的配置:<code>org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration</code>,即在 Spring啟動的時候加載:
那不同的存儲實作是如何加載各自的 CacheManger 的呢?我們就拿 Redis 來說,在配置類:
Redis 的配置類啟動的時候先檢查 CacheManager 是否有加載成功,有的話則去執行各種配置相關操作。上面代碼截出來了初始化 RedisCacheManager 的步驟。RedisCacheManager 實作了 CacheManager 接口。
當使用 RedisCacheManager 進行存儲的時候,通過被包裝的 Cache 對象來使用相關的存儲操作,我們看一下 RedisCache 對應的操作:
可以看到 Redis 的存儲使用的是普通的 KV 結構,value 的序列化方式是 yml 檔案中的配置。另外很重要的一點是 ttl 的配置,這裡能看到也是擷取配置檔案的屬性。是以當你想給每個 key 單獨設定過期時間的話就不能使用預設的 Redis 配置。而是需要自己實作 CacheManager。