spring-boot的spring-cache中的擴展redis緩存的ttl和key名


原文地址:spring-boot的spring-cache中的擴展redis緩存的ttl和key名

前提

spring-cache大家都用過,其中使用redis-cache大家也用過,至於如何使用怎么配置,本篇就不重點描述了。本篇主要解決2個問題,第一個問題使用redis做緩存時對每個key進行自定義的過期時間配置,第二個使用redis做緩存時@Cacheable(value = "value", key = "#p0") ,最后生成的key會在value和p0中間的有(::)2個冒號,與redis的key名一個冒號間隔的風格不符。

本篇以spring-boot 2.1.2和 spirng 5.1.4為基礎來講解。RedisCacheManage在spring-data-redis 2.x中相對於1.x的變動很大,本篇即在2.x的版本中實現。

redis cache的過期時間

我們都知道redis的過期時間,是用它做緩存或者做業務操作的靈性。在使用@Cacheable(value = "value", key = "#p0")注解時即可。具體的使用方法參考網上。

RedisCacheManager

我們先來看看RedisCacheManager,RedisCacheWriter接口是對redis操作進行包裝的一層低級的操作。defaultCacheConfig是redis的默認配置,在下一個選項卡中詳細介紹。initialCacheConfiguration是對各個單獨的緩存進行各自詳細的配置(過期時間就是在此配置的),allowInFlightCacheCreation是否允許創建不事先定義的緩存,如果不存在即使用默認配置。RedisCacheManagerBuilder使用橋模式,我們可以用它構建RedisCacheManager。

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

   private final RedisCacheWriter cacheWriter;
   private final RedisCacheConfiguration defaultCacheConfig;
   private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
   private final boolean allowInFlightCacheCreation;
   public static class RedisCacheManagerBuilder {}

}

AbstractTransactionSupportingCacheManager

AbstractTransactionSupportingCacheManager加入事務概念,將操作與事務綁定,包裝了一層事務。

public abstract class AbstractTransactionSupportingCacheManager extends AbstractCacheManager {

   private boolean transactionAware = false;

   public void setTransactionAware(boolean transactionAware) {
      this.transactionAware = transactionAware;
   }

   public boolean isTransactionAware() {
      return this.transactionAware;
   }

   @Override
   protected Cache decorateCache(Cache cache) {
      return (isTransactionAware() ? new TransactionAwareCacheDecorator(cache) : cache);
   }

}

RedisCacheConfiguration

ttl是過期時間,cacheNullValues是否允許存null值,keyPrefix緩存前綴規則,usePrefix是否允許使用前綴。keySerializationPair緩存key序列化,valueSerializationPair緩存值序列化此處最好自己使用jackson的序列號替代原生的jdk序列化,conversionService做轉換用的。

public class RedisCacheConfiguration {

   private final Duration ttl;
   private final boolean cacheNullValues;
   private final CacheKeyPrefix keyPrefix;
   private final boolean usePrefix;

   private final SerializationPair<String> keySerializationPair;
   private final SerializationPair<Object> valueSerializationPair;

   private final ConversionService conversionService;

}

RedisCacheManager

再來看看如何配置RedisCacheManager

RedisCacheAutoConfiguration

配置前通過RedisAutoConfiguration配置可以獲取到redis相關配置包括redisTemplate,因為spring-boot2中redis使用Lettuce作為客戶端,相關配置在LettuceConnectionConfiguration中。
在去加載CacheProperties和CustomCacheProperties配置。
通過RedisCacheManagerBuilder去構造RedisCacheManager,使用非加鎖的redis緩存操作,redis默認配置使用的是cacheProperties中的redis,最后根據我們自定義的customCacheProperties闊以針對單個的key設置單獨的redis緩存配置。

getDefaultRedisCacheConfiguration主要先通過RedisCacheConfiguration的默認創建方法defaultCacheConfig創建默認的配置,在通過getJackson2JsonRedisSerializer創建默認value格式化(使用jackson代替jdk序列化),然后通過redis緩存配置的是spring-cache的CacheProperties去修改配置項。

最后根據配置構建出RedisCacheConfiguration。

@Slf4j
@EnableCaching
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableConfigurationProperties({CacheProperties.class, CustomCacheProperties.class})
@ConditionalOnClass({Redis.class, RedisCacheConfiguration.class})
public class RedisCacheAutoConfiguration {

​    @Autowired
​    private CacheProperties cacheProperties;

​    @Bean
​    public RedisCacheManager redisCacheManager(CustomCacheProperties customCacheProperties,
​                                               RedisConnectionFactory redisConnectionFactory) {
​        RedisCacheConfiguration defaultConfiguration = getDefaultRedisCacheConfiguration();
​        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder
​                .fromCacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
​                .cacheDefaults(defaultConfiguration);

​        Map<String, RedisCacheConfiguration> map = Maps.newHashMap();
​        Optional.ofNullable(customCacheProperties)
​                .map(p -> p.getCustomCache())
​                .ifPresent(customCache -> {
​                    customCache.forEach((key, cache) -> {
​                        RedisCacheConfiguration cfg = handleRedisCacheConfiguration(cache, defaultConfiguration);
​                        map.put(key, cfg);
​                    });
​                });
​        builder.withInitialCacheConfigurations(map);
​        return builder.build();
​    }

​    private RedisCacheConfiguration getDefaultRedisCacheConfiguration() {
​        Redis redisProperties = cacheProperties.getRedis();
​        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

​        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = getJackson2JsonRedisSerializer();
​        config = config.serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()));
​        config = config.serializeValuesWith(SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
​        config = handleRedisCacheConfiguration(redisProperties, config);
​        return config;
​    }

​    private Jackson2JsonRedisSerializer getJackson2JsonRedisSerializer() {
​        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
​        ObjectMapper om = new ObjectMapper();
​        om.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.ANY);
​        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
​        om.setSerializationInclusion(Include.NON_NULL);
​        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
​        jackson2JsonRedisSerializer.setObjectMapper(om);
​        return jackson2JsonRedisSerializer;
​    }

​    private RedisCacheConfiguration handleRedisCacheConfiguration(Redis redisProperties,
​                                                                  RedisCacheConfiguration config) {
​        if (Objects.isNull(redisProperties)) {
​            return config;
​        }

​        if (redisProperties.getTimeToLive() != null) {
​            config = config.entryTtl(redisProperties.getTimeToLive());
​        }
​        if (redisProperties.getKeyPrefix() != null) {
​            config = config.computePrefixWith(cacheName -> cacheName + redisProperties.getKeyPrefix());
​        }
​        if (!redisProperties.isCacheNullValues()) {
​            config = config.disableCachingNullValues();
​        }
​        if (!redisProperties.isUseKeyPrefix()) {
​            config = config.disableKeyPrefix();
​        }
​        return config;
​    }

}

CustomCacheProperties

我們自定的緩存的配置,使用了現有的CacheProperties.Redis作為配置類。

@Data
@ConfigurationProperties(prefix = "damon.cache")
public class CustomCacheProperties {

​    private Map<String, CacheProperties.Redis> customCache;

}

Redis

Redis的key配置,過期時間,是否允許緩存空值默認可以,key的前綴,是否允許使用key前綴

public static class  {

   private Duration timeToLive;

   private boolean cacheNullValues = true;

   private String keyPrefix;

   private boolean useKeyPrefix = true;

}

yml配置

再來看看配置項

spring.cache.redis就為當前redis-cache的默認配置

底下的damon.cache就為自定義配置(默認20秒),如下配置了testA和 testB2個自定義key的過期時間(一個40秒,一個50秒)

spring:
  redis:
    host: localhost
    port: 6379
  cache:
​    redis:
      time-to-live: 20s

damon:
  cache:
    custom-cache:
      testA:
        time-to-live: 40s
      testB:
        time-to-live: 50s

redis-cache的key名調整

image-20190218104748207

從上述我們可以看出使用后,緩存過期時間可以自定義配置了,但是key名中間有2個冒號。

RedisCache

RedisCache中的createCacheKey方法是生成redis的key,從中可以看出是否使用prefix,使用的話通過prefixCacheKey方法生成,借用了redisCache配置項來生成

private final RedisCacheConfiguration cacheConfig;

protected String createCacheKey(Object key) {

   String convertedKey = convertKey(key);

   if (!cacheConfig.usePrefix()) {
      return convertedKey;
   }

   return prefixCacheKey(convertedKey);
}

private String prefixCacheKey(String key) {

   // allow contextual cache names by computing the key prefix on every call.
   return cacheConfig.getKeyPrefixFor(name) + key;
}

RedisCacheConfiguration

在redisCache配置項中使用getKeyPrefixFor方法來生成完整的redis的key名,通過 keyPrefix.compute來生成。

private final CacheKeyPrefix keyPrefix;

public String getKeyPrefixFor(String cacheName) {

   Assert.notNull(cacheName, "Cache name must not be null!");

   return keyPrefix.compute(cacheName);
}

CacheKeyPrefix

這里就看到我們使用處,而且看到了默認實現有2個冒號的實現。

其實是在RedisCacheConfiguration中有個默認實現方法,里面用的就是CacheKeyPrefix的默認實現。我們只有覆蓋此處即可。

@FunctionalInterface
public interface CacheKeyPrefix {

//計算在redis中的緩存名

String compute(String cacheName);

//默認實現,中間用的就是::

 static CacheKeyPrefix simple() {
return name -> name + "::";
   }
}

總結

參考上文,使用RedisCacheConfigurationcomputePrefixWith(cacheName -> cacheName + redisProperties.getKeyPrefix())實現key調整。

題外話

我們再來聊聊spring-cache,實際上其實它就是把緩存的使用給抽象了,在對緩存的具體實現的過程中給抽出來。其實最重要的就是CacheCacheManager2個接口,簡單的實現如SimpleCacheManager

歡迎關注我的微信公眾號

微信公眾號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM