深入淺出 Spring Cache 使用與整合(附源碼解析)
個人開發環境
java環境:Jdk1.8.0_60
編譯器:IntelliJ IDEA 2019.1
springCache官方文檔:https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/integration.html#cache
一、Spring緩存抽象
SpringCache產生的背景其實與Spring產生的背景有點類似。由於Java EE 系統框架臃腫、低效,代碼可觀性低,對象創建和依賴關系復雜,Spring框架出來了,目前基本上所有的Java后台項目都離不開Spring或SpringBoot(對Spring的進一步封裝簡化)。現在項目面臨高並發的問題越來越多,各類緩存的應用也增多,那么在通用的Spring框架上,就需要有一種更加便捷簡單的方式,來完成緩存的支持。
SpringCache本身是一個緩存體系的抽象實現,並沒有具體的緩存能力,要使用SpringCache還需要配合具體的緩存實現來完成。
- Cache接口為緩存的組件規范定義,包含緩存的各種操作集合;
- Cache接口下Spring提供了各種xxxCache的實現;如RedisCache,EhCacheCache ,ConcurrentMapCache等;
二、重要注解、參數
名稱 | 解釋 |
---|---|
Cache | 緩存接口,定義緩存操作。實現有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 緩存管理器,管理各種緩存(cache)組件 |
@Cacheable | 主要針對方法配置,能夠根據方法的請求參數對其進行緩存 |
@CacheEvict | 清空緩存 |
@CachePut | 保證方法被調用,又希望結果被緩存。 與@Cacheable區別在於是否每次都調用方法,常用於更新 |
@EnableCaching | 開啟基於注解的緩存 |
keyGenerator | 緩存數據時key生成策略 |
serialize | 緩存數據時value序列化策略 |
@CacheConfig | 統一配置本類的緩存注解的屬性 |
@Cacheable/@CachePut/@CacheEvict 主要的參數
名稱 | 解釋 |
---|---|
value | 緩存的名稱,在 spring 配置文件中定義,必須指定至少一個 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫, 如果不指定,則缺省按照方法的所有參數進行組合 例如: @Cacheable(value=”testcache”,key=”#id”) |
condition | 緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false, 只有為 true 才進行緩存/清除緩存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
unless | 否定緩存。當條件結果為TRUE時,就不會緩存。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”) |
allEntries (@CacheEvict ) | 是否清空所有緩存內容,缺省為 false,如果指定為 true, 則方法調用后將立即清空所有緩存 例如: @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation (@CacheEvict) | 是否在方法執行前就清空,缺省為 false,如果指定為 true, 則在方法還沒有執行的時候就清空緩存,缺省情況下,如果方法 執行拋出異常,則不會清空緩存 例如: @CachEvict(value=”testcache”,beforeInvocation=true) |
三、SpEL上下文數據
Spring Cache提供了一些供我們使用的SpEL上下文數據,下表直接摘自Spring官方文檔:
名稱 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root對象 | 當前被調用的方法名 | #root.methodname |
method | root對象 | 當前被調用的方法 | #root.method.name |
target | root對象 | 當前被調用的目標對象實例 | #root.target |
targetClass | root對象 | 當前被調用的目標對象的類 | #root.targetClass |
args | root對象 | 當前被調用的方法的參數列表 | #root.args[0] |
caches | root對象 | 當前方法調用使用的緩存列表 | #root.caches[0].name |
Argument Name | 執行上下文 | 當前被調用的方法的參數,如findArtisan(Artisan artisan),可以通過#artsian.id獲得參數 | #artsian.id |
result | 執行上下文 | 方法執行后的返回值(僅當方法執行后的判斷有效,如 unless cacheEvict的beforeInvocation=false) | #result |
四、實戰
1.導入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2.啟動類開啟緩存注解@EnableCaching
/**
* 1、開啟基於注解的緩存 @EnableCaching
* 2、標注緩存注解即可
* @Cacheable
* @CacheEvict
* @CachePut
*/
@SpringBootApplication
@EnableCaching //開啟緩存
public class CacheApplication{
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
3.緩存 @Cacheable
運行流程:
@Cacheable
方法運行之前,先去查詢Cache(緩存組件),按照cacheNames/value指定的名字獲取,第一次獲取緩存如果沒有Cache組件會自動創建。
源碼分析:
public @interface Cacheable {
// cacheNames/value:指定緩存組件的名字;將方法的返回結果放在哪個緩存中,是數組的方式,可以指定多個緩存
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// 緩存數據使用的key;可以用它來指定。默認是使用方法參數的值
String key() default "";
// key的生成器;可以自己指定key的生成器的組件id,key/keyGenerator:二選一使用;
String keyGenerator() default "";
// 指定緩存管理器;或者cacheResolver指定獲取解析器 作用得到緩存集合
String cacheManager() default "";
String cacheResolver() default "";
// 條件符合則緩存,編寫SpEL:condition = "#a0>1":第一個參數的值大於1的時候才進行緩存
String condition() default "";
// 條件符合則不緩存,編寫SpEL:unless = "#a0==2":如果第一個參數的值是2,結果不緩存;
String unless() default "";
// 是否使用異步模式,是否啟用異步模式
boolean sync() default false;
}
操練一下:
@Cacheable(value = "emp" ,key = "targetClass + methodName +#p0")
public Employee getEmp(Integer id){
System.out.println("查詢"+id+"號員工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
踩坑:
1、屬性value/cacheNames
是必需的,它指定了你的緩存存放在哪塊命名空間。
2、屬性key
是使用的spEL表達式
注意
:踩坑,如果你把methodName
換成method
運行會報錯,觀察它們的返回類型,原因在於methodName
是String
而methoh
是Method
。
Employee
實體類一定要實現序列化public class Employee implements Serializable
,否則會報java.io.NotSerializableException
異常。
4.更新 @CachePut
@CachePut
既調用方法,又更新緩存數據;同步更新緩存。簡單來說就是用戶修改數據同步更新緩存數據。
源碼分析:(皮一下,去事故與@Cacheable相同,不做過多解釋了,狗頭護體~~~)
public @interface CachePut {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
}
操練一下:
注意
該注解的value/cahceNames
和 key
必須與要更新的緩存相同,也就是與@Cacheable
相同。
@CachePut(value = "emp" ,key = "#employee.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
5.清除 @CacheEvict
@CachEvict
的作用 主要針對方法配置,能夠根據一定的條件對緩存進行清空 。
源碼分析:
public @interface CacheEvict {
// 同上同上同上
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
// 指定清除這個緩存中所有的數據,示例:@CachEvict(value=”emp”,allEntries=true)
boolean allEntries() default false;
/*
* 示例: @CachEvict(value=”emp”,beforeInvocation=true)
* 代表清除緩存操作是在方法運行之前執行,無論方法是否出現異常,緩存都清除
*
* 示例: @CachEvict(value=”emp”,beforeInvocation=false)(默認)
* 緩存的清除是否在方法之前執行 , 默認代表緩存清除操作是在方法執行之后執行;如果出現異常緩存就不會清除
*/
boolean beforeInvocation() default false;
}
操練一下:
//方法調用后清空所有緩存
@CacheEvict(value="accountCache", allEntries=true)
public void deleteEmp() {
employeeMapper.deleteAll();
}
6.組合 @Caching
有時候我們可能組合多個Cache注解使用,此時就需要@Caching組合多個注解標簽了。
源碼分析:
public @interface Caching {
// 用於指定多個緩存設置操作
Cacheable[] cacheable() default {};
// 用於指定多個緩存更新操作
CachePut[] put() default {};
// 用於指定多個緩存失效操作
CacheEvict[] evict() default {};
}
操練一下
// @Caching 定義復雜的緩存規則
@Caching(
cacheable = {
@Cacheable(value="emp",key = "#lastName")
},
put = {
@CachePut(value="emp",key = "#result.id"),
@CachePut(value="emp",key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
7.全局配置 @CacheConfig
當我們需要緩存的地方越來越多,可以使用@CacheConfig(cacheNames = {"emp"})
注解來統一指定value
的值,這時可省略value
,如果你在你的方法依舊寫上了value
,那么依然以方法的value
值為准。
源碼分析:
public @interface Caching {
// 用於指定多個緩存設置操作
Cacheable[] cacheable() default {};
// 用於指定多個緩存更新操作
CachePut[] put() default {};
// 用於指定多個緩存失效操作
CacheEvict[] evict() default {};
}
操練一下:
@CacheConfig(cacheNames = {"emp"}, /*keyGenerator = "cacheKeyGenerator"*/)
public class EmployeeServiceImpl implements EmployeeService {
@Override
@Cacheable(/*value = ‘emp’*/ key = "targetClass + methodName +#p0")
public Employee getEmp(Integer id){
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
}
8.主鍵生成策略 keyGenerator
操練一下:
創建CacheConfig配置類
@Configuration
public class CacheConfig {
/**
* 生成緩存主鍵策略 (方法名+參數)
*
* @return KeyGenerator
*/
@Bean("cacheKeyGenerator")
public KeyGenerator keyGenerator() {
return (target, method, params) -> (method.getName() + " [ " + Arrays.asList(params) + " ]");
}
}
可以在@CacheConfig
指定生成策略,也可以在@Cacheable/@CachePut/@CacheEvict
指定key生成策略
@CacheConfig(cacheNames = {"emp"}, keyGenerator = "cacheKeyGenerator")
public class EmployeeServiceImpl implements EmployeeService {
@Override
@Cacheable(/*value = ‘emp’,keyGenerator = "cacheKeyGenerator"*/)
public Employee getEmp(Integer id){
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
}