Spring Cache快取註解
阿新 • • 發佈:2020-07-28
[TOC]
# Spring Cache快取註解
> 本篇文章程式碼示例在[Spring Cache簡單實現](baidu.com)上的程式碼示例加以修改。
==只有使用public定義的方法才可以被快取,而private方法、protected 方法或者使用default 修飾符的方法都不能被快取。== 當在一個類上使用註解時,該類中每個公共方法的返回值都將被快取到指定的快取項中或者從中移除。
## `@Cacheable`
`@Cacheable`註解屬性一覽:
`@Cacheable`指定了被註解方法的返回值是可被快取的。其工作原理是Spring首先在快取中查詢資料,如果沒有則執行方法並快取結果,然後返回資料。
快取名是必須提供的,可以使用引號、Value或者cacheNames屬性來定義名稱。下面的定義展示了users快取的宣告及其註解的使用:
```java
@Cacheable("users")
//Spring 3.x
@Cacheable(value = "users")
//Spring 從4.0開始新增了value別名cacheNames比value更達意,推薦使用
@Cacheable(cacheNames = "users")
```
### 鍵生成器
快取的本質就是鍵/值對集合。==在預設情況下,快取抽象使用(方法簽名及引數值)作為一個鍵值,並將該鍵與方法呼叫的結果組成鍵/值對。== 如果在Cache註解上沒有指定key,
則Spring會使用KeyGenerator來生成一個key。
```java
package org.springframework.cache.interceptor;
import java.lang.reflect.Method;
@FunctionalInterface
public interface KeyGenerator {
Object generate(Object var1, Method var2, Object... var3);
}
```
Sping預設提供了SimpleKeyGenerator生成器。Spring 3.x之後廢棄了3.x 的DefaultKey
Generator而用SimpleKeyGenerator取代,原因是DefaultKeyGenerator在有多個入參時只是簡單地把所有入參放在一起使用hashCode()方法生成key值,這樣很容易造成key衝突。SimpleKeyGenerator使用一個複合鍵SimpleKey來解決這個問題。通過其原始碼可得知Spring生成key的規則。
```java
/**
* SimpleKeyGenerator原始碼的類路徑參見{@link org.springframework.cache.interceptor.SimpleKeyGenerator}
*/
```
從SimpleKeyGenerator的原始碼中可以發現其生成規則如下(附SimpleKey原始碼):
- 如果方法沒有入參,則使用SimpleKey.EMPTY作為key(key = new SimpleKey())。
- 如果只有一個入參,則使用該入參作為key(key = 入參的值)。
- 如果有多個入參,則返回包含所有入參的一個SimpleKey(key = new SimpleKey(params))。
```java
package org.springframework.cache.interceptor;
import java.io.Serializable;
import java.util.Arrays;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class SimpleKey implements Serializable {
public static final SimpleKey EMPTY = new SimpleKey(new Object[0]);
private final Object[] params;
private final int hashCode;
public SimpleKey(Object... elements) {
Assert.notNull(elements, "Elements must not be null");
this.params = new Object[elements.length];
System.arraycopy(elements, 0, this.params, 0, elements.length);
this.hashCode = Arrays.deepHashCode(this.params);
}
public boolean equals(Object other) {
return this == other || other instanceof SimpleKey && Arrays.deepEquals(this.params, ((SimpleKey)other).params);
}
public final int hashCode() {
return this.hashCode;
}
public String toString() {
return this.getClass().getSimpleName() + " [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]";
}
}
```
如需自定義鍵生成策略,可以通過實現`org.springframework.cache.interceptor.KeyGenerator`介面來定義自己實際需要的鍵生成器。示例如下,自定義了一個MyKeyGenerator類並且實現(implements)了KeyGenerator以實現自定義的鍵值生成器:
```java
package com.example.cache.springcache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKey;
import java.lang.reflect.Method;
/**
* @author: 部落格「成猿手冊」
* @description: 為方便演示,這裡自定義的鍵生成器只是在SimpleKeyGenerator基礎上加了一些logger列印以區別自定義的Spring預設的鍵值生成器;
*/
public class MyKeyGenerator implements KeyGenerator {
private static final Logger logger = LoggerFactory.getLogger(MyKeyGenerator.class);
@Override
public Object generate(Object o, Method method, Object... objects) {
logger.info("執行自定義鍵生成器");
return generateKey(objects);
}
public static Object generateKey(Object... params) {
if (params.length == 0) {
logger.debug("本次快取鍵名稱:{}", SimpleKey.EMPTY);
return SimpleKey.EMPTY;
} else {
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
logger.debug("本次快取鍵名稱:{}", params);
return param;
}
}
SimpleKey simpleKey = new SimpleKey(params);
logger.debug("本次快取鍵名稱:{}", simpleKey.toString());
return simpleKey;
}
}
}
```
同時在Spring配置檔案中配置:
屬性名 | 作用與描述 |
---|---|
cacheNames/value | 指定快取的名字,快取使用CacheManager管理多個快取Cache,這些Cache就是根據該屬性進行區分。對快取的真正增刪改查操作在Cache中定義,每個快取Cache都有自己唯一的名字。 |
key | 快取資料時的key的值,預設是使用方法所有入參的值,可以使用SpEL表示式表示key的值。 |
keyGenerator | 快取的生成策略(鍵生成器),和key二選一,作用是生成鍵值key,keyGenerator可自定義。 |
cacheManager | 指定快取管理器(例如ConcurrentHashMap、Redis等)。 |
cacheResolver | 和cacheManager作用一樣,使用時二選一。 |
condition |
指定快取的條件(對引數判斷,滿足什麼條件時才快取),可用SpEL表示式,例如:方法入參為物件user則表示式可以寫為condition =
"#user.age>18" ,表示當入參物件user的屬性age大於18才進行快取。
|
unless |
否定快取的條件(對結果判斷,滿足什麼條件時不快取),即滿足unless指定的條件時,對呼叫方法獲取的結果不進行快取,例如:unless =
"result==null" ,表示如果結果為null時不快取。
|
sync | 是否使用非同步模式進行快取,預設false。 |