Spring 快取註解這樣用,太香了!

2023-11-09 18:01:05

作者最近在開發公司專案時使用到 Redis 快取,並在翻看前人程式碼時,看到了一種關於 @Cacheable 註解的自定義快取有效期的解決方案,感覺比較實用,因此作者自己拓展完善了一番後分享給各位。

Spring 快取常規設定

Spring Cache 框架給我們提供了 @Cacheable 註解用於快取方法返回內容。但是 @Cacheable 註解不能定義快取有效期。這樣的話在一些需要自定義快取有效期的場景就不太實用。

按照 Spring Cache 框架給我們提供的 RedisCacheManager 實現,只能在全域性設定快取有效期。這裡給大家看一個常規的 CacheConfig 快取設定類,程式碼如下,

@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
    ...

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    private RedisSerializer<Object> valueSerializer() {
        return new GenericFastJsonRedisSerializer();
    }

    public static final String CACHE_PREFIX = "crowd:";

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 設定序列化(解決亂碼的問題)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                //設定key為String
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                //設定value為自動轉Json的Object
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
                .computePrefixWith(name -> CACHE_PREFIX + name  + ":")
                .entryTtl(Duration.ofSeconds(600));
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisConnectionFactory));
        return new RedisCacheManager(redisCacheWriter, config);
    }
}

這裡面簡單對 RedisCacheConfiguration 快取設定做一下說明:

  1. serializeKeysWith():設定 Redis 的 key 的序列化規則。
  2. erializeValuesWith():設定 Redis 的 value 的序列化規則。
  3. computePrefixWith():計算 Redis 的 key 字首。
  4. entryTtl():全域性設定 @Cacheable 註解快取的有效期。

那麼使用如上設定生成的 Redis 快取 key 名稱是什麼樣得嘞?這裡用開源專案 crowd-adminConfigServiceImpl 類下 getValueByKey(String key) 方法舉例,

@Cacheable(value = "configCache", key = "#root.methodName + '_' + #root.args[0]")
@Override
public String getValueByKey(String key) {
    QueryWrapper<Config> wrapper = new QueryWrapper<>();
    wrapper.eq("configKey", key);
    Config config = getOne(wrapper);
    if (config == null) {
        return null;
    }
    return config.getConfigValue();
}

執行此方法後,Redis 中快取 key 名稱如下,

crowd:configCache:getValueByKey_sys.name

ttl 過期時間是 287,跟我們全域性設定的 300 秒基本是一致的。此時假如我們想把 getValueByKey 方法的快取有效期單獨設定為 600 秒,那我們該如何操作嘞?

@Cacheable 註解預設是沒有提供有關快取有效期設定的。想要單獨修改 getValueByKey 方法的快取有效期只能修改全域性的快取有效期。那麼有沒有別的方法能夠為 getValueByKey 方法單獨設定快取有效期嘞?當然是有的,大家請往下看。

自定義 MyRedisCacheManager 快取

其實我們可以通過自定義 MyRedisCacheManager 類繼承 Spring Cache 提供的 RedisCacheManager 類後,重寫 createRedisCache(String name, RedisCacheConfiguration cacheConfig) 方法來完成自定義快取有效期的功能,程式碼如下,

public class MyRedisCacheManager extends RedisCacheManager {
    public MyRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }

    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        String[] array = StringUtils.split(name, "#");
        name = array[0];
         // 解析 @Cacheable 註解的 value 屬性用以單獨設定有效期
        if (array.length > 1) {
            long ttl = Long.parseLong(array[1]);
            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
        }
        return super.createRedisCache(name, cacheConfig);
    }
}

MyRedisCacheManager 類邏輯如下,

  1. 繼承 Spring Cache 提供的 RedisCacheManager 類。
  2. 重寫 createRedisCache(String name, RedisCacheConfiguration cacheConfig) 方法。
  3. 解析 name 引數,根據 # 字串進行分割,獲取快取 key 名稱以及快取有效期。
  4. 重新設定快取 key 名稱以及快取有效期。
  5. 呼叫父類別的 createRedisCache(name, cacheConfig) 方法來完成快取寫入。

接著我們修改下 CacheConfig 類的 cacheManager 方法用以使用 MyRedisCacheManager 類。程式碼如下,

@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    return new MyRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), defaultCacheConfig());
}

private RedisCacheConfiguration defaultCacheConfig() {
    return RedisCacheConfiguration.defaultCacheConfig()
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
            .computePrefixWith(name -> CACHE_PREFIX + name  + ":")
            .entryTtl(Duration.ofSeconds(600));
}

最後在使用 @Cacheable 註解時,在原有 value 屬性的 configCache 值後新增 #600,單獨標識快取有效期。程式碼如下,

@Cacheable(value = "configCache#600", key = "#root.methodName + '_' + #root.args[0]")
@Override
public String getValueByKey(String key) {
   ...
}

看下 getValueByKey 方法生成的 Redis 快取 key 有效期是多久。如下,

OK,看到是 590 秒有效期後,我們就大功告成了。到這裡我們就完成了對 @Cacheable 註解的自定義快取有效期功能開發。

關注公眾號【waynblog】每週分享技術乾貨、開源專案、實戰經驗、國外優質文章翻譯等,您的關注將是我的更新動力!