JetCache主要通過@Cached和@CreateCache實現緩存,@Cached是在接口方法或者類方法上添加緩存,一般以參數爲key,以返回值爲value存入緩存中。@CreateCache是直接創建一個緩存實例,然後調用put(T key, T value)、get(T key)等方法實現緩存。
(1)如果是SpringBoot框架開發:
pom文件:
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.5.6</version>
</dependency>
application.yml文件:
jetcache:
statIntervalMinutes: 15
areaInCacheName: false
local:
default:
type: linkedhashmap
keyConvertor: fastjson
remote:
default:
type: redis
keyConvertor: fastjson
valueEncoder: java
valueDecoder: java
poolConfig:
minIdle: 5
maxIdle: 20
maxTotal: 50
host: 127.0.0.1
port: 6379
啓動類:
EnableMethodCache,EnableCreateCacheAnnotation這兩個註解分別激活Cached和CreateCache註解,其他和標準的Spring Boot程序是一樣的。這個類可以直接main方法運行。
package com.company.mypackage;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableMethodCache(basePackages = "com.company.mypackage") //激活@Cached
@EnableCreateCacheAnnotation //激活@CreateCache
public class MySpringBootApp {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApp.class);
}
}
(2)如果沒有使用SpringBoot:
pom文件:
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-anno</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-redis</artifactId>
<version>2.5.6</version>
</dependency>
配置JetCacheConfig類,激活@CreateCache和@Cached註解。
package com.company.mypackage;
import java.util.HashMap;
import java.util.Map;
import com.alicp.jetcache.anno.CacheConsts;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import com.alicp.jetcache.anno.support.GlobalCacheConfig;
import com.alicp.jetcache.anno.support.SpringConfigProvider;
import com.alicp.jetcache.embedded.EmbeddedCacheBuilder;
import com.alicp.jetcache.embedded.LinkedHashMapCacheBuilder;
import com.alicp.jetcache.redis.RedisCacheBuilder;
import com.alicp.jetcache.support.FastjsonKeyConvertor;
import com.alicp.jetcache.support.JavaValueDecoder;
import com.alicp.jetcache.support.JavaValueEncoder;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.util.Pool;
@Configuration
@EnableMethodCache(basePackages = "com.company.mypackage")
@EnableCreateCacheAnnotation
public class JetCacheConfig {
@Bean
public Pool<Jedis> pool(){
GenericObjectPoolConfig pc = new GenericObjectPoolConfig();
pc.setMinIdle(2);
pc.setMaxIdle(10);
pc.setMaxTotal(10);
return new JedisPool(pc, "localhost", 6379);
}
@Bean
public SpringConfigProvider springConfigProvider() {
return new SpringConfigProvider();
}
@Bean
public GlobalCacheConfig config(SpringConfigProvider configProvider, Pool<Jedis> pool){
Map localBuilders = new HashMap();
EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder
.createLinkedHashMapCacheBuilder()
.keyConvertor(FastjsonKeyConvertor.INSTANCE);
localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);
Map remoteBuilders = new HashMap();
RedisCacheBuilder remoteCacheBuilder = RedisCacheBuilder.createRedisCacheBuilder()
.keyConvertor(FastjsonKeyConvertor.INSTANCE)
.valueEncoder(JavaValueEncoder.INSTANCE)
.valueDecoder(JavaValueDecoder.INSTANCE)
.jedisPool(pool);
remoteBuilders.put(CacheConsts.DEFAULT_AREA, remoteCacheBuilder);
GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig();
globalCacheConfig.setConfigProvider(configProvider);
globalCacheConfig.setLocalCacheBuilders(localBuilders);
globalCacheConfig.setRemoteCacheBuilders(remoteBuilders);
globalCacheConfig.setStatIntervalMinutes(15);
globalCacheConfig.setAreaInCacheName(false);
return globalCacheConfig;
}
}
方法一:創建緩存實例
通過@Cached直接創建一個緩存實例,默認超時100s
@CreateCache(expire = 100)
private Cache<Long, UserDO> userCache;
調用api方法實現緩存:
V get(K key)
void put(K key, V value);
boolean putIfAbsent(K key, V value); //多級緩存MultiLevelCache不支持此方法
boolean remove(K key);
<T> T unwrap(Class<T> clazz);//2.2版本前,多級緩存MultiLevelCache不支持此方法
Map<K,V> getAll(Set<? extends K> keys);
void putAll(Map<? extends K,? extends V> map);
void removeAll(Set<? extends K> keys);
這些方法和JSR107的javax.cache.Cache接口一致,下面是jetCache特有的API:
V computeIfAbsent(K key, Function<K, V> loader)
當key對應的緩存不存在時,使用loader加載。通過這種方式,loader的加載時間可以被統計到。
V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull)
當key對應的緩存不存在時,使用loader加載。cacheNullWhenLoaderReturnNull參數指定了當loader加載出來時null值的時候,是否要進行緩存(有時候即使是null值也是通過很繁重的查詢纔得到的,需要緩存)。
V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull, long expire, TimeUnit timeUnit)
當key對應的緩存不存在時,使用loader加載。cacheNullWhenLoaderReturnNull參數指定了當loader加載出來時null值的時候,是否要進行緩存(有時候即使是null值也是通過很繁重的查詢纔得到的,需要緩存)。expire和timeUnit指定了緩存的超時時間,會覆蓋緩存的默認超時時間。
void put(K key, V value, long expire, TimeUnit timeUnit)
put操作,expire和timeUnit指定了緩存的超時時間,會覆蓋緩存的默認超時時間。
AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit)
boolean tryLockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action)
非堵塞的嘗試獲取一個鎖,如果對應的key還沒有鎖,返回一個AutoReleaseLock,否則立即返回空。如果Cache實例是本地的,它是一個本地鎖,在本JVM中有效;如果是redis等遠程緩存,它是一個不十分嚴格的分佈式鎖。鎖的超時時間由expire和timeUnit指定。多級緩存的情況會使用最後一級做tryLock操作。用法如下:
// 使用try-with-resource方式,可以自動釋放鎖
try(AutoReleaseLock lock = cache.tryLock("MyKey",100, TimeUnit.SECONDS)){
if(lock != null){
// do something
}
}
上面的代碼有個潛在的坑是忘記判斷if(lock!=null),所以一般可以直接用tryLockAndRun更加簡單
boolean hasRun = tryLockAndRun("MyKey",100, TimeUnit.SECONDS), () -> {
// do something
};
tryLock內部會在訪問遠程緩存失敗時重試,會自動釋放,而且不會釋放不屬於自己的鎖,比你自己做這些要簡單。當然,基於遠程緩存實現的任何分佈式鎖都不會是嚴格的分佈式鎖,不能和基於ZooKeeper或Consul做的鎖相比。
還有大寫的API:
V get(K key)這樣的方法雖然用起來方便,但有功能上的缺陷,當get返回null的時候,無法斷定是對應的key不存在,還是訪問緩存發生了異常,所以JetCache針對部分操作提供了另外一套API,提供了完整的返回值,包括:
CacheGetResult<V> GET(K key);
MultiGetResult<K, V> GET_ALL(Set<? extends K> keys);
CacheResult PUT(K key, V value);
CacheResult PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);
CacheResult PUT_ALL(Map<? extends K, ? extends V> map);
CacheResult PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit);
CacheResult REMOVE(K key);
CacheResult REMOVE_ALL(Set<? extends K> keys);
CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);
這些方法的特徵是方法名爲大寫,與小寫的普通方法對應,提供了完整的返回值,用起來也稍微繁瑣一些。例如:
CacheGetResult<OrderDO> r = cache.GET(orderId);
if( r.isSuccess() ){
OrderDO order = r.getValue();
} else if (r.getResultCode() == CacheResultCode.NOT_EXISTS) {
System.out.println("cache miss:" + orderId);
} else if(r.getResultCode() == CacheResultCode.EXPIRED) {
System.out.println("cache expired:" + orderId));
} else {
System.out.println("cache get error:" + orderId);
}
屬性值說明:
屬性 默認值 說明
area “default” 如果需要連接多個緩存系統,可在配置多個cache area,這個屬性指定要使用的那個area的name
name 未定義 指定緩存的名稱,不是必須的,如果沒有指定,會使用類名+方法名。name會被用於遠程緩存的key前綴。另外在統計中,一個簡短有意義的名字會提高可讀性。如果兩個@CreateCache的name和area相同,它們會指向同一個Cache實例
expire 未定義 該Cache實例的默認超時時間定義,註解上沒有定義的時候會使用全局配置,如果此時全局配置也沒有定義,則取無窮大
timeUnit TimeUnit.SECONDS 指定expire的單位
cacheType CacheType.REMOTE 緩存的類型,包括CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定義爲BOTH,會使用LOCAL和REMOTE組合成兩級緩存
localLimit 未定義 如果cacheType爲CacheType.LOCAL或CacheType.BOTH,這個參數指定本地緩存的最大元素數量,以控制內存佔用。註解上沒有定義的時候會使用全局配置,如果此時全局配置也沒有定義,則取100
serialPolicy 未定義 如果cacheType爲CacheType.REMOTE或CacheType.BOTH,指定遠程緩存的序列化方式。JetCache內置的可選值爲SerialPolicy.JAVA和SerialPolicy.KRYO。註解上沒有定義的時候會使用全局配置,如果此時全局配置也沒有定義,則取SerialPolicy.JAVA
keyConvertor 未定義 指定KEY的轉換方式,用於將複雜的KEY類型轉換爲緩存實現可以接受的類型,JetCache內置的可選值爲KeyConvertor.FASTJSON和KeyConvertor.NONE。NONE表示不轉換,FASTJSON通過fastjson將複雜對象KEY轉換成String。如果註解上沒有定義,則使用全局配置。
方法二:創建方法緩存
使用@Cached方法可以爲一個方法添加上緩存,@CacheUpdate用於更新緩存,@CacheInvalidate用於移除緩存元素。JetCache通過Spring AOP生成代理,來支持緩存功能。註解可以加在接口方法上也可以加在類方法上,但需要保證是個Spring bean。
@Cached:系統調用該接口方法時檢測到@Cached標籤,首先會根據key去調用get方法獲取value值,如果存在value值則直接將值返回,如果不存在key,則會執行代碼查詢結果,並自動調用get方法將返回值存入緩存中。
public interface UserService {
@Cached(name="userCache.", key="#userId", expire = 3600)
User getUserById(long userId);
@CacheUpdate(name="userCache.", key="#user.userId", value="#user")
void updateUser(User user);
@CacheInvalidate(name="userCache.", key="#userId")
void deleteUser(long userId);
}
key使用Spring的SpEL腳本來指定。如果要使用參數名(比如這裏的key="#userId"),項目編譯設置target必須爲1.8格式,並且指定javac的-parameters參數,否則就要使用key="args[0]"這樣按下標訪問的形式。
@CacheUpdate和@CacheInvalidate的name和area屬性必須和@Cached相同,name屬性還會用做cache的key前綴。
@Cached註解和@CreateCache的屬性非常類似,但是多幾個:
屬性 默認值 說明
area “default” 如果在配置中配置了多個緩存area,在這裏指定使用哪個area
name 未定義 指定緩存的唯一名稱,不是必須的,如果沒有指定,會使用類名+方法名。name會被用於遠程緩存的key前綴。另外在統計中,一個簡短有意義的名字會提高可讀性。
key 未定義 使用SpEL指定key,如果沒有指定會根據所有參數自動生成。
expire 未定義 超時時間。如果註解上沒有定義,會使用全局配置,如果此時全局配置也沒有定義,則爲無窮大
timeUnit TimeUnit.SECONDS 指定expire的單位
cacheType CacheType.REMOTE 緩存的類型,包括CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定義爲BOTH,會使用LOCAL和REMOTE組合成兩級緩存
localLimit 未定義 如果cacheType爲LOCAL或BOTH,這個參數指定本地緩存的最大元素數量,以控制內存佔用。如果註解上沒有定義,會使用全局配置,如果此時全局配置也沒有定義,則爲100
localExpire 未定義 僅當cacheType爲BOTH時適用,爲內存中的Cache指定一個不一樣的超時時間,通常應該小於expire
serialPolicy 未定義 指定遠程緩存的序列化方式。可選值爲SerialPolicy.JAVA和SerialPolicy.KRYO。如果註解上沒有定義,會使用全局配置,如果此時全局配置也沒有定義,則爲SerialPolicy.JAVA
keyConvertor 未定義 指定KEY的轉換方式,用於將複雜的KEY類型轉換爲緩存實現可以接受的類型,當前支持KeyConvertor.FASTJSON和KeyConvertor.NONE。NONE表示不轉換,FASTJSON可以將複雜對象KEY轉換成String。如果註解上沒有定義,會使用全局配置。
enabled true 是否激活緩存。例如某個dao方法上加緩存註解,由於某些調用場景下不能有緩存,所以可以設置enabled爲false,正常調用不會使用緩存,在需要的地方可使用CacheContext.enableCache在回調中激活緩存,緩存激活的標記在ThreadLocal上,該標記被設置後,所有enable=false的緩存都被激活
cacheNullValue false 當方法返回值爲null的時候是否要緩存
condition 未定義 使用SpEL指定條件,如果表達式返回true的時候纔去緩存中查詢
postCondition 未定義 使用SpEL指定條件,如果表達式返回true的時候才更新緩存,該評估在方法執行後進行,因此可以訪問到#result
@CacheInvalidate註解說明:
屬性 默認值 說明
area “default” 如果在配置中配置了多個緩存area,在這裏指定使用哪個area,指向對應的@Cached定義。
name 未定義 指定緩存的唯一名稱,指向對應的@Cached定義。
key 未定義 使用SpEL指定key
condition 未定義 使用SpEL指定條件,如果表達式返回true才執行刪除,可訪問方法結果#result
@CacheUpdate註解說明:
屬性 默認值 說明
area “default” 如果在配置中配置了多個緩存area,在這裏指定使用哪個area,指向對應的@Cached定義。
name 未定義 指定緩存的唯一名稱,指向對應的@Cached定義。
key 未定義 使用SpEL指定key
value 未定義 使用SpEL指定value
condition 未定義 使用SpEL指定條件,如果表達式返回true才執行更新,可訪問方法結果#result
使用@CacheUpdate和@CacheInvalidate的時候,相關的緩存操作可能會失敗(比如網絡IO錯誤),所以指定緩存的超時時間是非常重要的。
@CacheRefresh註解說明:
屬性 默認值 說明
refresh 未定義 刷新間隔
timeUnit TimeUnit.SECONDS 時間單位
stopRefreshAfterLastAccess 未定義 指定該key多長時間沒有訪問就停止刷新,如果不指定會一直刷新
refreshLockTimeout 60秒 類型爲BOTH/REMOTE的緩存刷新時,同時只會有一臺服務器在刷新,這臺服務器會在遠程緩存放置一個分佈式鎖,此配置指定該鎖的超時時間
@CachePenetrationProtect註解:
當緩存訪問未命中的情況下,對併發進行的加載行爲進行保護。 當前版本實現的是單JVM內的保護,即同一個JVM中同一個key只有一個線程去加載,其它線程等待結果。
如果想了解更多內容,可查閱JetCache官網wiki文檔:https://github.com/alibaba/jetcache/wiki