jetcache官網教程

更多學習資料,可以關注下方微信公衆號,回覆關鍵字:Java高級資料    。  即可免費獲取完整的Java高級開發工程師視頻課程。

jetcache

簡介

JetCache是一個基於Java的緩存系統封裝,提供統一的API和註解來簡化緩存的使用。 JetCache提供了比SpringCache更加強大的註解,可以原生的支持TTL、兩級緩存、分佈式自動刷新,還提供了Cache接口用於手工緩存操作。當前有四個實現,RedisCacheTairCache(此部分未在github開源)、CaffeineCache(in memory)和一個簡易的LinkedHashMapCache(in memory),要添加新的實現也是非常簡單的。

全部特性:

  • 通過統一的API訪問Cache系統
  • 通過註解實現聲明式的方法緩存,支持TTL和兩級緩存
  • 通過註解創建並配置Cache實例
  • 針對所有Cache實例和方法緩存的自動統計
  • Key的生成策略和Value的序列化策略是可以配置的
  • 分佈式緩存自動刷新,分佈式鎖 (2.2+)
  • 異步Cache API (2.2+,使用Redis的lettuce客戶端時)
  • Spring Boot支持

要求

JetCache需要JDK1.8Spring Framework4.0.8以上版本。Spring Boot爲可選,需要1.1.9以上版本。如果不使用註解(僅使用jetcache-core),Spring Framework也是可選的,此時使用方式與Guava/Caffeinecache類似。

文檔目錄

依賴哪個jar

  • jetcache-anno-api:定義jetcache的註解和常量,不傳遞依賴。如果你想把Cached註解加到接口上,又不希望你的接口jar傳遞太多依賴,可以讓接口jar依賴jetcache-anno-api。
  • jetcache-core:核心api,完全通過編程來配置操作Cache,不依賴Spring。兩個內存中的緩存實現LinkedHashMapCacheCaffeineCache也由它提供。
  • jetcache-anno:基於Spring提供@Cached和@CreateCache註解支持。
  • jetcache-redis:使用jedis提供Redis支持。
  • jetcache-redis-lettuce(需要JetCache2.3以上版本):使用lettuce提供Redis支持,實現了JetCache異步訪問緩存的的接口。
  • jetcache-starter-redis:Spring Boot方式的Starter,基於Jedis。
  • jetcache-starter-redis-lettuce(需要JetCache2.3以上版本):Spring Boot方式的Starter,基於Lettuce。

快速入門

創建緩存實例

通過@CreateCache註解創建一個緩存實例,默認超時時間是100

@CreateCache(expire = 100)
private Cache<Long, UserDO> userCache;

用起來就像map一樣

UserDO user = userCache.get(123L);
userCache.put(123L, user);
userCache.remove(123L);

創建一個兩級(內存+遠程)的緩存,內存中的元素個數限制在50個。

@CreateCache(name = "UserService.userCache", expire = 100, cacheType = CacheType.BOTH, localLimit = 50)
private Cache<Long, UserDO> userCache;

name屬性不是必須的,但是起個名字是個好習慣,展示統計數據的使用,會使用這個名字。如果同一個area兩個@CreateCachename配置一樣,它們生成的Cache將指向同一個實例。

創建方法緩存

使用@Cached方法可以爲一個方法添加上緩存。JetCache通過Spring AOP生成代理,來支持緩存功能。註解可以加在接口方法上也可以加在類方法上,但需要保證是個Springbean

public interface UserService {
    @Cached(name="UserService.getUserById", expire = 3600)
    User getUserById(long userId);
}

基本配置(使用Spring Boot)

如果使用SpringBoot,可以按如下的方式配置。

POM

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.4.4</version>
</dependency>

配置一個springboot風格的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

然後創建一個App類放在業務包的根下,EnableMethodCacheEnableCreateCacheAnnotation這兩個註解分別激活CachedCreateCache註解,其他和標準的SpringBoot程序是一樣的。這個類可以直接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")
@EnableCreateCacheAnnotation
public class MySpringBootApp {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApp.class);
    }
}

未使用SpringBoot的配置方式

如果沒有使用springboot,可以按下面的方式配置(這裏使用jedis客戶端連接redis爲例)。

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-anno</artifactId>
    <version>2.4.4</version>
</dependency>
<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-redis</artifactId>
    <version>2.4.4</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;
    }
}

進一步閱讀

  • CreateCache的詳細使用說明可以看這裏
  • 使用@CacheCache創建的Cache接口實例,它的API使用可以看這裏
  • 關於方法緩存(@Cached, @CacheUpdate, @CacheInvalidate)的詳細使用看這裏
  • 詳細的配置說明看這裏

基本Cache API

簡介

JetCache2.0的核心是com.alicp.jetcache.Cache接口(以下簡寫爲Cache),它提供了部分類似於javax.cache.CacheJSR107)的API操作。沒有完整實現JSR107的原因包括:

  1. 希望維持API的簡單易用。
  2. 對於特定的遠程緩存系統來說,javax.cache.Cache中定義的有些操作無法高效率的實現,比如一些原子操作方法和類似removeAll()這樣的方法。
  3. JSR107比較複雜,完整實現要做的工作很多。

JSR107 style API

以下是Cache接口中和JSR107javax.cache.Cache接口一致的方法,除了不會拋出異常,這些方法的簽名和行爲和JSR107都是一樣的。

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);

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值也是通過很繁重的查詢纔得到的,需要緩存)。expiretimeUnit指定了緩存的超時時間,會覆蓋緩存的默認超時時間。

void put(K key, V value, long expire, TimeUnit timeUnit)

put操作,expiretimeUnit指定了緩存的超時時間,會覆蓋緩存的默認超時時間。

AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit)
boolean tryLockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action)

非堵塞的嘗試獲取一個鎖,如果對應的key還沒有鎖,返回一個AutoReleaseLock,否則立即返回空。如果Cache實例是本地的,它是一個本地鎖,在本JVM中有效;如果是redis等遠程緩存,它是一個不十分嚴格的分佈式鎖。鎖的超時時間由expiretimeUnit指定。多級緩存的情況會使用最後一級做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內部會在訪問遠程緩存失敗時重試,會自動釋放,而且不會釋放不屬於自己的鎖,比你自己做這些要簡單。當然,基於遠程緩存實現的任何分佈式鎖都不會是嚴格的分佈式鎖,不能和基於ZooKeeperConsul做的鎖相比。

大寫API

Vget(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);
}

通過@CreateCache註解創建Cache實例

簡介

Springbean中使用@CreateCache註解創建一個Cache實例。例如

@CreateCache(expire = 100)
private Cache<Long, UserDO> userCache;

@CreateCache屬性表

屬性

默認值

說明

area

“default”

如果需要連接多個緩存系統,可在配置多個cache area,這個屬性指定要使用的那個areaname

name

未定義

指定緩存的名稱,不是必須的,如果沒有指定,會使用類名+方法名。name會被用於遠程緩存的key前綴。另外在統計中,一個簡短有意義的名字會提高可讀性。如果兩個@CreateCachenamearea相同,它們會指向同一個Cache實例

expire

未定義

Cache實例的默認超時時間定義,註解上沒有定義的時候會使用全局配置,如果此時全局配置也沒有定義,則取無窮大

timeUnit

TimeUnit.SECONDS

指定expire的單位

cacheType

CacheType.REMOTE

緩存的類型,包括CacheType.REMOTECacheType.LOCALCacheType.BOTH。如果定義爲BOTH,會使用LOCALREMOTE組合成兩級緩存

localLimit

未定義

如果cacheTypeCacheType.LOCALCacheType.BOTH,這個參數指定本地緩存的最大元素數量,以控制內存佔用。註解上沒有定義的時候會使用全局配置,如果此時全局配置也沒有定義,則取100

serialPolicy

未定義

如果cacheTypeCacheType.REMOTECacheType.BOTH,指定遠程緩存的序列化方式。JetCache內置的可選值爲SerialPolicy.JAVASerialPolicy.KRYO。註解上沒有定義的時候會使用全局配置,如果此時全局配置也沒有定義,則取SerialPolicy.JAVA

keyConvertor

未定義

指定KEY的轉換方式,用於將複雜的KEY類型轉換爲緩存實現可以接受的類型,JetCache內置的可選值爲KeyConvertor.FASTJSONKeyConvertor.NONENONE表示不轉換,FASTJSON通過fastjson將複雜對象KEY轉換成String。如果註解上沒有定義,則使用全局配置。

默認值

對於以上未定義默認值的參數,如果沒有指定,將使用yml中指定的全局配置,請參考配置詳解

 

通過註解實現方法緩存

JetCache方法緩存和SpringCache比較類似,它原生提供了TTL支持,以保證最終一致,並且支持二級緩存。JetCache2.4以後支持基於註解的緩存更新和刪除。

spring環境下,使用@Cached註解可以爲一個方法添加緩存,@CacheUpdate用於更新緩存,@CacheInvalidate用於移除緩存元素。註解可以加在接口上也可以加在類上,加註解的類必須是一個spring bean,例如:

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使用SpringSpEL腳本來指定。如果要使用參數名(比如這裏的key="#userId"),項目編譯設置target必須爲1.8格式,並且指定javac-parameters參數,否則就要使用key="args[0]"這樣按下標訪問的形式。

@CacheUpdate@CacheInvalidatenamearea屬性必須和@Cached相同,name屬性還會用做cachekey前綴。

@Cached註解和@CreateCache的屬性非常類似,但是多幾個:

屬性

默認值

說明

area

“default”

如果在配置中配置了多個緩存area,在這裏指定使用哪個area

name

未定義

指定緩存的唯一名稱,不是必須的,如果沒有指定,會使用類名+方法名。name會被用於遠程緩存的key前綴。另外在統計中,一個簡短有意義的名字會提高可讀性。

key

未定義

使用SpEL指定key,如果沒有指定會根據所有參數自動生成。

expire

未定義

超時時間。如果註解上沒有定義,會使用全局配置,如果此時全局配置也沒有定義,則爲無窮大

timeUnit

TimeUnit.SECONDS

指定expire的單位

cacheType

CacheType.REMOTE

緩存的類型,包括CacheType.REMOTECacheType.LOCALCacheType.BOTH。如果定義爲BOTH,會使用LOCALREMOTE組合成兩級緩存

localLimit

未定義

如果cacheTypeLOCALBOTH,這個參數指定本地緩存的最大元素數量,以控制內存佔用。如果註解上沒有定義,會使用全局配置,如果此時全局配置也沒有定義,則爲100

serialPolicy

未定義

指定遠程緩存的序列化方式。可選值爲SerialPolicy.JAVASerialPolicy.KRYO。如果註解上沒有定義,會使用全局配置,如果此時全局配置也沒有定義,則爲SerialPolicy.JAVA

keyConvertor

未定義

指定KEY的轉換方式,用於將複雜的KEY類型轉換爲緩存實現可以接受的類型,當前支持KeyConvertor.FASTJSONKeyConvertor.NONENONE表示不轉換,FASTJSON可以將複雜對象KEY轉換成String。如果註解上沒有定義,會使用全局配置。

enabled

true

是否激活緩存。例如某個dao方法上加緩存註解,由於某些調用場景下不能有緩存,所以可以設置enabledfalse,正常調用不會使用緩存,在需要的地方可使用CacheContext.enableCache在回調中激活緩存,緩存激活的標記在ThreadLocal上,該標記被設置後,所有enable=false的緩存都被激活

cacheNullValue

false

當方法返回值爲null的時候是否要緩存

condition

未定義

使用SpEL指定條件,如果表達式返回true的時候才進行緩存

@CacheInvalidate註解說明:

屬性

默認值

說明

area

“default”

如果在配置中配置了多個緩存area,在這裏指定使用哪個area,指向對應的@Cached定義。

name

未定義

指定緩存的唯一名稱,指向對應的@Cached定義。

key

未定義

使用SpEL指定key

condition

未定義

使用SpEL指定條件,如果表達式返回true才執行刪除

@CacheUpdate註解說明:

屬性

默認值

說明

area

“default”

如果在配置中配置了多個緩存area,在這裏指定使用哪個area,指向對應的@Cached定義。

name

未定義

指定緩存的唯一名稱,指向對應的@Cached定義。

key

未定義

使用SpEL指定key

value

未定義

使用SpEL指定value

condition

未定義

使用SpEL指定條件,如果表達式返回true才執行更新

使用@CacheUpdate@CacheInvalidate的時候,相關的緩存操作可能會失敗(比如網絡IO錯誤),所以指定緩存的超時時間是非常重要的。

@CacheRefresh註解說明:

屬性

默認值

說明

refresh

未定義

刷新間隔

timeUnit

TimeUnit.SECONDS

時間單位

stopRefreshAfterLastAccess

未定義

指定該key多長時間沒有訪問就停止刷新,如果不指定會一直刷新

refreshLockTimeout

60

類型爲BOTH/REMOTE的緩存刷新時,同時只會有一臺服務器在刷新,這臺服務器會在遠程緩存放置一個分佈式鎖,此配置指定該鎖的超時時間

對於以上未定義默認值的參數,如果沒有指定,將使用yml中指定的全局配置,全局配置請參考配置說明

 

配置詳解

yml配置文件案例(如果沒使用springboot,直接配置GlobalCacheConfig是類似的,參考快速入門教程):

jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  hiddenPackages: com.alibaba
  local:
    default:
      type: caffeine
      limit: 100
      keyConvertor: fastjson
      expireAfterWriteInMillis: 100000
    otherArea:
      type: linkedhashmap
      limit: 100
      keyConvertor: none
      expireAfterWriteInMillis: 100000
  remote:
    default:
      type: redis
      keyConvertor: fastjson
      valueEncoder: java
      valueDecoder: java
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: ${redis.host}
      port: ${redis.port}
    otherArea:
      type: redis
      keyConvertor: fastjson
      valueEncoder: kryo
      valueDecoder: kryo
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: ${redis.host}
      port: ${redis.port}

配置通用說明如下

屬性

默認值

說明

jetcache.statIntervalMinutes

0

統計間隔,0表示不統計

jetcache.areaInCacheName

true

jetcache-annocacheName作爲遠程緩存key前綴,2.4.3以前的版本總是把areaName加在cacheName中,因此areaName也出現在key前綴中。2.4.4以後可以配置,爲了保持遠程key兼容默認值爲true,但是新項目的話false更合理些。

jetcache.hiddenPackages

@Cached@CreateCache自動生成name的時候,爲了不讓name太長,hiddenPackages指定的包名前綴被截掉

jetcache.[local|remote].${area}.type

緩存類型。tairredis爲當前支持的遠程緩存;linkedhashmapcaffeine爲當前支持的本地緩存類型

jetcache.[local|remote].${area}.keyConvertor

key轉換器的全局配置,當前只有一個已經實現的keyConvertorfastjson。僅當使用@CreateCache且緩存類型爲LOCAL時可以指定爲none,此時通過equals方法來識別key。方法緩存必須指定keyConvertor

jetcache.[local|remote].${area}.valueEncoder

java

序列化器的全局配置。僅remote類型的緩存需要指定,可選javakryo

jetcache.[local|remote].${area}.valueDecoder

java

序列化器的全局配置。僅remote類型的緩存需要指定,可選javakryo

jetcache.[local|remote].${area}.limit

100

每個緩存實例的最大元素的全局配置,僅local類型的緩存需要指定。注意是每個緩存實例的限制,而不是全部,比如這裏指定100,然後用@CreateCache創建了兩個緩存實例(並且註解上沒有設置localLimit屬性),那麼每個緩存實例的限制都是100

jetcache.[local|remote].${area}.expireAfterWriteInMillis

無窮大

以毫秒爲單位指定超時時間的全局配置(以前爲defaultExpireInMillis)

jetcache.local.${area}.expireAfterAccessInMillis

0

需要jetcache2.2以上,以毫秒爲單位,指定多長時間沒有訪問,就讓緩存失效,當前只有本地緩存支持。0表示不使用這個功能。

上表中${area}對應@Cached@CreateCachearea屬性。注意如果註解上沒有指定area,默認值是"default"

關於緩存的超時時間,有多個地方指定,澄清說明一下:

  1. put等方法上指定了超時時間,則以此時間爲準
  2. put等方法上未指定超時時間,使用Cache實例的默認超時時間
  3. Cache實例的默認超時時間,通過在@CreateCache和@Cached上的expire屬性指定,如果沒有指定,使用yml中定義的全局配置,例如@Cached(cacheType=local)使用jetcache.local.default.expireAfterWriteInMillis,如果仍未指定則是無窮大

高級Cache API

CacheBuilder

CacheBuilder提供使用代碼直接構造Cache實例的方式,使用說明看這裏。如果沒有使用Spring,可以使用CacheBuilder,否則沒有必要直接使用CacheBuilder

異步API

JetCache2.2版本開始,所有的大寫API返回的CacheResult都支持異步。當底層的緩存實現支持異步的時候,大寫API返回的結果都是異步的。當前支持異步的實現只有jetcacheredis-luttece實現,其他的緩存實現(內存中的、TairJedis等),所有的異步接口都會同步堵塞,這樣API仍然是兼容的。

以下的例子假設使用redis-luttece訪問cache,例如:

CacheGetResult<UserDO> r = cache.GET(userId);

這一行代碼執行完以後,緩存操作可能還沒有完成,如果此時調用r.isSuccess()或者r.getValue()或者r.getMessage()將會堵塞直到緩存操作完成。如果不想被堵塞,並且需要在緩存操作完成以後執行後續操作,可以這樣做:

CompletionStage<ResultData> future = r.future();
future.thenRun(() -> {
    if(r.isSuccess()){
        System.out.println(r.getValue());
    }
});

以上代碼將會在緩存操作異步完成後,在完成異步操作的線程中調用thenRun中指定的回調。CompletionStageJava8新增的功能,如果對此不太熟悉可以先查閱相關的文檔。需要注意的是,既然已經選擇了異步的開發方式,在回調中不能調用堵塞方法,以免堵塞其他的線程(回調方法很可能是在event loop線程中執行的)。

部分小寫的api不需要任何修改,就可以直接享受到異步開發的好處。比如putremoveAll方法,由於它們沒有返回值,所以此時就直接優化成異步調用,能夠減少RT;而get方法由於需要取返回值,所以仍然會堵塞。

自動load(read through)

LoadingCache類提供了自動load的功能,它是一個包裝,基於decorator模式,也實現了Cache接口。如果CacheBuilder指定了loader,那麼buildCache返回的Cache實例就是經過LoadingCache包裝過的。例如:

Cache<Long,UserDO> userCache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder()
                .loader(key -> loadUserFromDatabase(key))
                .buildCache();

LoadingCachegetgetAll方法,在緩存未命中的情況下,會調用loader,如果loader拋出一場,getgetAll會拋出CacheInvokeException

需要注意

  1. GET、GET_ALL這類大寫API只純粹訪問緩存,不會調用loader。
  2. 如果使用多級緩存,loader應該安裝在MultiLevelCache上,不要安裝在底下的緩存上。

註解的屬性只能是常量,所以沒有辦法在CreateCache註解中指定loader,不過我們可以這樣:

@CreateCache
private Cache<Long,UserDO> userCache;

@PostConstruct
public void init(){
    userCache.config().setLoader(this::loadUserFromDatabase);
}

@CreateCache總是初始化一個經過LoadingCache包裝的Cache,直接在config中設置loader,可以實時生效。

自動刷新緩存

JetCache2.2版本開始,RefreshCache基於decorator模式提供了自動刷新的緩存的能力,目的是爲了防止緩存失效時造成的雪崩效應打爆數據庫。同時設置了loaderrefreshPolicy的時候,CacheBuilderbuildCache方法返回的Cache實例經過了RefreshCache的包裝。

RefreshPolicy policy = RefreshPolicy.newPolicy(1, TimeUnit.MINUTES)
                .stopRefreshAfterLastAccess(30, TimeUnit.MINUTES);
Cache<String, Long> orderSumCache = LinkedHashMapCacheBuilder
                .createLinkedHashMapCacheBuilder()
                .loader(key -> loadOrderSumFromDatabase(key))
                .refreshPolicy(policy)
                .buildCache();

對一些key比較少,實時性要求不高,加載開銷非常大的緩存場景,適合使用自動刷新。上面的代碼指定每分鐘刷新一次,30分鐘如果沒有訪問就停止刷新。如果緩存是redis或者多級緩存最後一級是redis,緩存加載行爲是全局唯一的,也就是說不管有多少臺服務器,同時只有一個服務器在刷新,這是通過tryLock實現的,目的是爲了降低後端的加載負擔。

LoadingCache一樣,使用@CreateCache時,我們需要這樣來添加自動刷新功能

@CreateCache
private Cache<String, Long> orderSumCache;

@PostConstruct
public void init(){
    RefreshPolicy policy = RefreshPolicy.newPolicy(1, TimeUnit.MINUTES)
                          .stopRefreshAfterLastAccess(30, TimeUnit.MINUTES);
    orderSumCache.config().setLoader(this::loadOrderSumFromDatabase);
    orderSumCache.config().setRefreshPolicy(policy);
}

Redis支持(兩種redis客戶端二選一即可)

使用jedis客戶端連接redis

redis有多種java版本的客戶端,JetCache2.2以前使用jedis客戶端訪問redis。從JetCache2.2版本開始,增加了對luttece客戶端的支持,jetcacheluttece支持提供了異步操作和redis集羣支持。

如果選用jedis訪問redis,對應的maven artifactjetcache-redisjetcache-starter-redis(spring boot)

spring boot環境下的jedis支持

application.yml文件如下(這裏省去了local相關的配置):

jetcache: 
  areaInCacheName: false
  remote:
    default:
      type: redis
      keyConvertor: fastjson
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: ${redis.host}
      port: ${redis.port}
      #sentinels: 127.0.0.1:26379 , 127.0.0.1:26380, 127.0.0.1:26381
      #masterName: mymaster

 

如果需要直接操作JedisPool,可以通過以下方式獲取

@Bean(name = "defaultPool")
@DependsOn(RedisAutoConfiguration.AUTO_INIT_BEAN_NAME)//jetcache2.2+
//@DependsOn("redisAutoInit")//jetcache2.1
public JedisPoolFactory defaultPool() {
    return new JedisPoolFactory("remote.default", JedisPool.class);
}

然後可以直接使用

@Autowired
private Pool<Jedis> defaultPool;

也可以用Cache接口上的<T> Tunwrap(Class<T> clazz)方法來獲取JedisPool,參見RedisCache.unwrap源代碼。

不使用spring boot

 

@Configuration
@EnableMethodCache(basePackages = "com.company.mypackage")
@EnableCreateCacheAnnotation
public class JetCacheConfig {

    @Bean
    public Pool<Jedis> pool(){
        // build jedis pool ...
    }

    @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;
    }
}

Builder API

如果不通過@CreateCache@Cached註解,可以通過下面的方式創建RedisCache。通過註解創建的緩存會自動設置keyPrefix,這裏是手工創建緩存,對於遠程緩存需要設置keyPrefix屬性,以免不同Cache實例的key發生衝突。

GenericObjectPoolConfig pc = new GenericObjectPoolConfig();
pc.setMinIdle(2);
pc.setMaxIdle(10);
pc.setMaxTotal(10);
JedisPool pool = new JedisPool(pc, "localhost", 6379);

Cache<Long,OrderDO> orderCache = RedisCacheBuilder.createRedisCacheBuilder()
                .keyConvertor(FastjsonKeyConvertor.INSTANCE)
                .valueEncoder(JavaValueEncoder.INSTANCE)
                .valueDecoder(JavaValueDecoder.INSTANCE)
                .jedisPool(pool)
                .keyPrefix("orderCache")
                .expireAfterWrite(200, TimeUnit.SECONDS)
                .buildCache();

常見問題

如果遇到這個錯誤

java.lang.NoSuchMethodError: redis.clients.jedis.JedisPool.<init>(Lorg/apache/commons/pool2/impl/GenericObjectPoolConfig;Ljava/lang/String;IILjava/lang/String;ILjava/lang/String;Z)V

請確保jedis的版本在2.9.0以上,spring boot 1.5以下版本的spring-boot-dependencies會引入較低版本的jedis,可以在自己的pom中強制直接依賴jedis版本2.9.0

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

使用lettuce客戶端連接redis

redis有多種java版本的客戶端,JetCache2.2以前使用jedis客戶端訪問redis。從JetCache2.2版本開始,增加了對lettuce客戶端的支持,JetCachelettuce支持提供了異步操作和redis集羣支持。

使用lettuce訪問redis,對應的maven artifactjetcache-redis-lettucejetcache-starter-redis-lettucelettuce使用Netty建立單個連接連redis,所以不需要配置連接池。

注意:新發布的lettuce5更換了groupId和包名,2.3版本的JetCache同時支持lettuce45jetcache-redis-lettucejetcache-starter-redis-lettuce提供lettuce5支持,jetcache-redis-lettuce4jetcache-starter-redis-lettuce4提供lettuce4支持。

注意:JetCache2.2版本中,lettuce單詞存在錯誤的拼寫,錯寫爲“luttece”,該錯誤存在於包名、類名和配置中,2.3已經改正。

spring boot環境下的lettuce支持

application.yml文件如下(這裏省去了local相關的配置):

jetcache: 
  areaInCacheName: false
  remote:
    default:
      type: redis.lettuce
      keyConvertor: fastjson
      uri: redis://127.0.0.1:6379/

 

如果使用sentinel做自動主備切換,uri可以配置爲redis-sentinel://127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381/?sentinelMasterId=mymaster

如果是集羣:

jetcache: 
  areaInCacheName: false
  remote:
    default:
      type: redis.lettuce
      keyConvertor: fastjson
      uri:
        - redis://127.0.0.1:7000
        - redis://127.0.0.1:7001
        - redis://127.0.0.1:7002

 

如果需要直接使用lettuceRedisClient

@Bean(name = "defaultClient")
@DependsOn(RedisLettuceAutoConfiguration.AUTO_INIT_BEAN_NAME)
public LettuceFactory defaultClient() {
    return new LettuceFactory("remote.default", RedisClient.class);
}

然後可以直接使用

@Autowired
private RedisClient defaultClient;

也可以用Cache接口上的<T> Tunwrap(Class<T> clazz)方法來獲取RedisClientRedisCommands等。參考RedisLettuceCache.unwrap源代碼。

不使用spring boot

 

@Configuration
@EnableMethodCache(basePackages = "com.company.mypackage")
@EnableCreateCacheAnnotation
public class JetCacheConfig {

    @Bean
    public RedisClient redisClient(){
        RedisClient client = RedisClient.create("redis://127.0.0.1");
        return client;
    }

    @Bean
    public SpringConfigProvider springConfigProvider() {
        return new SpringConfigProvider();
    }

    @Bean
    public GlobalCacheConfig config(SpringConfigProvider configProvider,RedisClient redisClient){
        Map localBuilders = new HashMap();
        EmbeddedCacheBuilder localBuilder = LinkedHashMapCacheBuilder
                .createLinkedHashMapCacheBuilder()
                .keyConvertor(FastjsonKeyConvertor.INSTANCE);
        localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);

        Map remoteBuilders = new HashMap();
        RedisLettuceCacheBuilder remoteCacheBuilder = RedisLettuceCacheBuilder.createRedisLettuceCacheBuilder()
                .keyConvertor(FastjsonKeyConvertor.INSTANCE)
                .valueEncoder(JavaValueEncoder.INSTANCE)
                .valueDecoder(JavaValueDecoder.INSTANCE)
                .redisClient(redisClient);
        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;
    }
}

 

builder API

 

如果不通過@CreateCache@Cached註解,可以通過下面的方式創建Cache。通過註解創建的緩存會自動設置keyPrefix,這裏是手工創建緩存,對於遠程緩存需要設置keyPrefix屬性,以免不同Cache實例的key發生衝突。

RedisClient client = RedisClient.create("redis://127.0.0.1");

Cache<Long,OrderDO> orderCache = RedisLettuceCacheBuilder.createRedisLettuceCacheBuilder()
                .keyConvertor(FastjsonKeyConvertor.INSTANCE)
                .valueEncoder(JavaValueEncoder.INSTANCE)
                .valueDecoder(JavaValueDecoder.INSTANCE)
                .redisClient(client)
                .keyPrefix("orderCache")
                .expireAfterWrite(200, TimeUnit.SECONDS)
                .buildCache();

內存緩存LinkedHashMapCache和CaffeineCache

本地緩存當前有兩個實現。如果自己用jetcache-coreCache API,可以不指定keyConvertor,此時本地緩存使用equals方法來比較key如果使用jetcache-anno中的@Cached@CreateCache等註解,必須指定keyConvertor

LinkedHashMapCache

LinkedHashMapCacheJetCache中實現的一個最簡單的Cache,使用LinkedHashMapLRU方式淘汰。

Cache<Long, OrderDO> cache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder()
                .limit(100)
                .expireAfterWrite(200, TimeUnit.SECONDS)
                .buildCache();

CaffeineCache

caffeine cache的介紹看這裏,它是guava cache的後續作品。

Cache<Long, OrderDO> cache = CaffeineCacheBuilder.createCaffeineCacheBuilder()
                .limit(100)
                .expireAfterWrite(200, TimeUnit.SECONDS)
                .buildCache();

統計

yml中的jetcache.statIntervalMinutes大於0時,通過@CreateCache@Cached配置出來的Cache自帶監控。JetCache會按指定的時間定期通過logger輸出統計信息。默認輸出信息類似如下:

2017-01-12 19:00:00,001 INFO  support.StatInfoLogger - jetcache stat from 2017-01-12 18:59:00,000 to 2017-01-12 19:00:00,000
cache                                                |       qps|   rate|           get|           hit|          fail|        expire|avgLoadTime|maxLoadTime
-----------------------------------------------------+----------+-------+--------------+--------------+--------------+--------------+-----------+-----------
default_AlicpAppChannelManager.getAlicpAppChannelById|      0.00|  0.00%|             0|             0|             0|             0|        0.0|          0
default_ChannelManager.getChannelByAccessToten       |     30.02| 99.78%|         1,801|         1,797|             0|             4|        0.0|          0
default_ChannelManager.getChannelByAppChannelId      |      8.30| 99.60%|           498|           496|             0|             1|        0.0|          0
default_ChannelManager.getChannelById                |      6.65| 98.75%|           399|           394|             0|             4|        0.0|          0
default_ConfigManager.getChannelConfig               |      1.97| 96.61%|           118|           114|             0|             4|        0.0|          0
default_ConfigManager.getGameConfig                  |      0.00|  0.00%|             0|             0|             0|             0|        0.0|          0
default_ConfigManager.getInstanceConfig              |     43.98| 99.96%|         2,639|         2,638|             0|             0|        0.0|          0
default_ConfigManager.getInstanceConfigSettingMap    |      2.45| 70.75%|           147|           104|             0|            43|        0.0|          0
default_GameManager.getGameById                      |      1.33|100.00%|            80|            80|             0|             0|        0.0|          0
default_GameManager.getGameUrlByUrlKey               |      7.33|100.00%|           440|           440|             0|             0|        0.0|          0
default_InstanceManager.getInstanceById              |     30.98| 99.52%|         1,859|         1,850|             0|             0|        0.0|          0
default_InstanceManager.getInstanceById_local        |     30.98| 96.40%|         1,859|         1,792|             0|            67|        0.0|          0
default_InstanceManager.getInstanceById_remote       |      1.12| 86.57%|            67|            58|             0|             6|        0.0|          0
default_IssueDao.getIssueById                        |      7.62| 81.40%|           457|           372|             0|            63|        0.0|          0
default_IssueDao.getRecentOnSaleIssues               |      8.00| 85.21%|           480|           409|             0|            71|        0.0|          0
default_IssueDao.getRecentOpenAwardIssues            |      2.52| 82.78%|           151|           125|             0|            26|        0.0|          0
default_PrizeManager.getPrizeMap                     |      0.82|100.00%|            49|            49|             0|             0|        0.0|          0
default_TopicManager.getOnSaleTopics                 |      0.97|100.00%|            58|            58|             0|             0|        0.0|          0
default_TopicManager.getOnSaleTopics_local           |      0.97| 91.38%|            58|            53|             0|             5|        0.0|          0
default_TopicManager.getOnSaleTopics_remote          |      0.08|100.00%|             5|             5|             0|             0|        0.0|          0
default_TopicManager.getTopicByTopicId               |      2.90| 98.85%|           174|           172|             0|             0|        0.0|          0
default_TopicManager.getTopicByTopicId_local         |      2.90| 96.55%|           174|           168|             0|             6|        0.0|          0
default_TopicManager.getTopicByTopicId_remote        |      0.10| 66.67%|             6|             4|             0|             2|        0.0|          0
default_TopicManager.getTopicList                    |      0.02|100.00%|             1|             1|             0|             0|        0.0|          0
default_TopicManager.getTopicList_local              |      0.02|  0.00%|             1|             0|             0|             1|        0.0|          0
default_TopicManager.getTopicList_remote             |      0.02|100.00%|             1|             1|             0|             0|        0.0|          0
-----------------------------------------------------+----------+-------+--------------+--------------+--------------+--------------+-----------+-----------

 

只有使用computeIfAbsent方法或者@Cached註解纔會統計loadTime。用get方法取緩存,沒有命中的話自己去數據庫load,顯然是無法統計到的。

 

如果需要定製輸出,可以這樣做:

    @Bean
    public SpringConfigProvider springConfigProvider() {
        return new SpringConfigProvider(){
            public Consumer<StatInfo> statCallback() {
                // return new StatInfoLogger(false);
                ... // 實現自己的logger
            }
        };
    }

JetCachestatIntervalMinutes指定的週期,定期調用statCallback返回着這個Consumer,傳入的StatInfo是已經統計好的數據。這個方法默認的實現是:

returnnew StatInfoLogger(false);

StatInfoLogger的構造參數設置爲true會有更詳細的統計信息,包括put等操作的統計。StatInfoLogger輸出的是給人讀的信息,你也可以自定義logger將日誌輸出成特定格式,然後通過日誌系統統一收集和統計。

如果想要讓jetcache的日誌輸出到獨立的文件中,在使用logback的情況下可以這樣配置:

<appender name="JETCACHE_LOGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>jetcache.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>jetcache.log.%d{yyyy-MM-dd}</fileNamePattern>
        <maxHistory>30</maxHistory>
    </rollingPolicy>

    <encoder>
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender>

<logger name="com.alicp.jetcache" level="INFO" additivity="false">
    <appender-ref ref="JETCACHE_LOGFILE" />
</logger>

Builder:未使用Spring4(或者spring)的時候,通過Builder手工構造Cache

JetCache2版本的@Cached@CreateCache等註解都是基於Spring4.X版本實現的,在沒有Spring支持的情況下,註解將不能使用。但是可以直接使用JetCacheAPI來創建、管理、監控Cache,多級緩存也可以使用。

創建緩存

創建緩存的操作類似guava/caffeinecache,例如下面的代碼創建基於內存的LinkedHashMapCache

Cache<String, Integer> cache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder()
                .limit(100)
                .expireAfterWrite(200, TimeUnit.SECONDS)
                .buildCache();

創建RedisCache

GenericObjectPoolConfig pc = new GenericObjectPoolConfig();
        pc.setMinIdle(2);
        pc.setMaxIdle(10);
        pc.setMaxTotal(10);
        JedisPool pool = new JedisPool(pc, "localhost", 6379);
Cache<Long, OrderDO> orderCache = RedisCacheBuilder.createRedisCacheBuilder()
                .keyConvertor(FastjsonKeyConvertor.INSTANCE)
                .valueEncoder(JavaValueEncoder.INSTANCE)
                .valueDecoder(JavaValueDecoder.INSTANCE)
                .jedisPool(pool)
                .keyPrefix("orderCache")
                .expireAfterWrite(200, TimeUnit.SECONDS)
                .buildCache();

多級緩存

2.2以後通過下面的方式創建多級緩存:

Cache multiLevelCache = MultiLevelCacheBuilder.createMultiLevelCacheBuilder()
      .addCache(memoryCache, redisCache)
      .expireAfterWrite(100, TimeUnit.SECONDS)
      .buildCache();

實際上,使用MultiLevelCache可以創建多級緩存,它的構造函數接收的是一個Cache數組(可變參數)。

如果是2.2之前的版本:

Cache memoryCache = ...
Cache redisCache = ...
Cache multiLevelCache = new MultiLevelCache(memoryCache, redisCache);

監控統計

如果要對Cache進行監控統計:

Cache orderCache = ...
CacheMonitor orderCacheMonitor = new DefaultCacheMonitor("OrderCache");
orderCache.config().getMonitors().add(orderCacheMonitor); // jetcache 2.2+, or call builder.addMonitor() before buildCache()
// Cache<Long, Order> monitedOrderCache = new MonitoredCache(orderCache, orderCacheMonitor); //before jetcache 2.2
int resetTime = 1;
boolean verboseLog = false;
DefaultCacheMonitorManager cacheMonitorManager = new DefaultCacheMonitorManager(resetTime, TimeUnit.SECONDS, verboseLog);
cacheMonitorManager.add(orderCacheMonitor);
cacheMonitorManager.start();

首先創建一個CacheMonitor,每個DefaultCacheMonitor只能用於一個Cache。當DefaultCacheMonitorManager啓動以後,會使用slf4j按指定的時間定期輸出統計信息到日誌中(簡版輸出格式參見統計),DefaultCacheMonitor構造時指定的名字會作爲輸出時cache的名字。

在組裝多級緩存的過程中,可以給每個緩存安裝一個Monitor,這樣可以監控每一級的命中情況。

也可以自己對統計信息進行處理,調用下面的構造方法創建DefaultCacheMonitorManager

public DefaultCacheMonitorManager(int resetTime, TimeUnit resetTimeUnit, Consumer<StatInfo> stat

 

開發者文檔

clone下來以後,可以按maven項目導入ideaeclipse

跑通單元測試,需要在本地運行redis,先安裝docker,然後用下面的命令運行redis-sentinel

docker run --rm -it -p 6379-6381:6379-6381 -p 26379-26381:26379-26381 areyouok/redis-sentinel

接下來mvn cleantest可以跑通所有測試,如果在IDE裏面,可能還需要給javac設置-parameters參數。需要注意的是機器繁忙時單元測試有可能會失敗,因爲很多單元測試使用了sleep,爲了不讓單元測試運行的時間過長,sleep的時間都設置的比較短,這樣機器卡頓時可能導致檢查失敗,不過對於正常機器這並不經常發生。

使用snapshot版本,在自己的pom裏面加上:

<repositories>
        <repository>
            <id>sonatype-nexus-snapshots</id>
            <name>Sonatype Nexus Snapshots</name>
            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

 

升級和兼容性指南

 

2.5.0

  • 從2.3.3及更低版本升級到2.5.0會發生ClassCastException(如果你使用了MultiLevelCache或者cacheType.CacheType.BOTH)。 解決辦法是先升級到2.4.4並且發佈到生產環境,然後再升級到2.5.0。
  • 子類的註解會覆蓋接口和父類。

更多學習資料,可以關注下方微信公衆號,回覆關鍵字:Java高級資料    。  即可免費獲取完整的Java高級開發工程師視頻課程。

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