JetCache快速使用以及@Cached和@CreateCache的配置說明

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
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章