原文地址:spring-boot的spring-cache中的擴展redis緩存的ttl和key名java
前提
spring-cache你們都用過,其中使用redis-cache你們也用過,至於如何使用怎麼配置,本篇就不重點描述了。本篇主要解決2個問題,第一個問題使用redis作緩存時對每一個key進行自定義的過時時間配置,第二個使用redis作緩存時
@Cacheable(value = "value", key = "#p0")
,最後生成的key會在value和p0中間的有(::)2個冒號,與redis的key名一個冒號間隔的風格不符。redis
本篇以spring-boot 2.1.2和 spirng 5.1.4爲基礎來說解。RedisCacheManage在spring-data-redis 2.x中相對於1.x的變更很大,本篇即在2.x的版本中實現。spring
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加入事務概念,將操做與事務綁定,包裝了一層事務。app
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作轉換用的。ide
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
再來看看如何配置RedisCacheManagerspring-boot
RedisCacheAutoConfiguration
配置前經過RedisAutoConfiguration
配置能夠獲取到redis相關配置包括redisTemplate,由於spring-boot2中redis使用Lettuce做爲客戶端,相關配置在LettuceConnectionConfiguration
中。 在去加載CacheProperties和CustomCacheProperties配置。 經過RedisCacheManagerBuilder去構造RedisCacheManager,使用非加鎖的redis緩存操做,redis默認配置使用的是cacheProperties中的redis,最後根據咱們自定義的customCacheProperties闊以針對單個的key設置單獨的redis緩存配置。ui
getDefaultRedisCacheConfiguration主要先經過RedisCacheConfiguration的默認建立方法defaultCacheConfig
建立默認的配置,在經過getJackson2JsonRedisSerializer建立默認value格式化(使用jackson代替jdk序列化),而後經過redis緩存配置的是spring-cache的CacheProperties去修改配置項。this
最後根據配置構建出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
和 testB
2個自定義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名調整
從上述咱們能夠看出使用後,緩存過時時間能夠自定義配置了,可是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 + "::"; } }
總結
參考上文,使用RedisCacheConfiguration
的computePrefixWith(cacheName -> cacheName + redisProperties.getKeyPrefix())
實現key調整。
題外話
咱們再來聊聊spring-cache,實際上其實它就是把緩存的使用給抽象了,在對緩存的具體實現的過程當中給抽出來。其實最重要的就是Cache
和CacheManager
2個接口,簡單的實現如SimpleCacheManager
。
<center>歡迎關注個人微信公衆號</center>