From 0cf5c3e2fc70362dde06c0a18627fcf09d2f0fd5 Mon Sep 17 00:00:00 2001 From: i544693 Date: Wed, 24 May 2023 16:17:37 +0800 Subject: [PATCH 1/5] update: expose ttl for dynamic config. --- .../redis/cache/RedisCacheConfiguration.java | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java b/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java index da9b06215b..bd9acc61ec 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java @@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.function.Consumer; +import java.util.function.Supplier; import org.springframework.cache.Cache; import org.springframework.cache.interceptor.SimpleKey; @@ -40,6 +41,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author John Blum + * @author Koy Zhuang * @since 2.0 */ public class RedisCacheConfiguration { @@ -106,7 +108,7 @@ public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader c registerDefaultConverters(conversionService); - return new RedisCacheConfiguration(Duration.ZERO, DEFAULT_CACHE_NULL_VALUES, DEFAULT_USE_PREFIX, + return new RedisCacheConfiguration(() -> Duration.ZERO, DEFAULT_CACHE_NULL_VALUES, DEFAULT_USE_PREFIX, CacheKeyPrefix.simple(), SerializationPair.fromSerializer(RedisSerializer.string()), SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService); @@ -119,17 +121,17 @@ public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader c private final ConversionService conversionService; - private final Duration ttl; + private final Supplier ttlProvider; private final SerializationPair keySerializationPair; private final SerializationPair valueSerializationPair; @SuppressWarnings("unchecked") - private RedisCacheConfiguration(Duration ttl, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix, + private RedisCacheConfiguration(Supplier ttl, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix, SerializationPair keySerializationPair, SerializationPair valueSerializationPair, ConversionService conversionService) { - this.ttl = ttl; + this.ttlProvider = ttl; this.cacheNullValues = cacheNullValues; this.usePrefix = usePrefix; this.keyPrefix = keyPrefix; @@ -165,7 +167,7 @@ public RedisCacheConfiguration computePrefixWith(CacheKeyPrefix cacheKeyPrefix) Assert.notNull(cacheKeyPrefix, "Function for computing prefix must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, DEFAULT_USE_PREFIX, cacheKeyPrefix, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, DEFAULT_USE_PREFIX, cacheKeyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -178,7 +180,7 @@ public RedisCacheConfiguration computePrefixWith(CacheKeyPrefix cacheKeyPrefix) * @return new {@link RedisCacheConfiguration}. */ public RedisCacheConfiguration disableCachingNullValues() { - return new RedisCacheConfiguration(ttl, DO_NOT_CACHE_NULL_VALUES, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, DO_NOT_CACHE_NULL_VALUES, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -191,12 +193,12 @@ public RedisCacheConfiguration disableCachingNullValues() { */ public RedisCacheConfiguration disableKeyPrefix() { - return new RedisCacheConfiguration(ttl, cacheNullValues, DO_NOT_USE_PREFIX, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, DO_NOT_USE_PREFIX, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } /** - * Set the ttl to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache. + * Set the constant ttl to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache. * * @param ttl must not be {@literal null}. * @return new {@link RedisCacheConfiguration}. @@ -205,10 +207,24 @@ public RedisCacheConfiguration entryTtl(Duration ttl) { Assert.notNull(ttl, "TTL duration must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(() -> ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } + /** + * Set the ttl Provider to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache. + * + * @param ttlProvider must not be {@literal null}, and the ttl should not be {@literal null} + * @return new {@link RedisCacheConfiguration}. + */ + public RedisCacheConfiguration entryTtlProvider(Supplier ttlProvider) { + + Assert.notNull(ttlProvider, "ttlProvider must not be null"); + + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + valueSerializationPair, conversionService); + } + /** * Define the {@link SerializationPair} used for de-/serializing cache keys. * @@ -219,7 +235,7 @@ public RedisCacheConfiguration serializeKeysWith(SerializationPair keySe Assert.notNull(keySerializationPair, "KeySerializationPair must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -233,7 +249,7 @@ public RedisCacheConfiguration serializeValuesWith(SerializationPair valueSer Assert.notNull(valueSerializationPair, "ValueSerializationPair must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -247,7 +263,7 @@ public RedisCacheConfiguration withConversionService(ConversionService conversio Assert.notNull(conversionService, "ConversionService must not be null"); - return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration(ttlProvider, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } @@ -304,6 +320,10 @@ public SerializationPair getValueSerializationPair() { * @return The expiration time (ttl) for cache entries. Never {@literal null}. */ public Duration getTtl() { + Duration ttl = ttlProvider.get(); + + Assert.notNull(ttl, "TTL duration must not be null"); + return ttl; } From 6c923300dd2ec023e14cfeaddbf6d35d163405b2 Mon Sep 17 00:00:00 2001 From: i544693 Date: Wed, 24 May 2023 16:37:30 +0800 Subject: [PATCH 2/5] tests: add tests for ttl provider. --- .../RedisCacheConfigurationUnitTests.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java b/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java index a86c9b23fb..614c114b01 100644 --- a/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java @@ -24,6 +24,10 @@ import org.springframework.instrument.classloading.ShadowingClassLoader; import org.springframework.lang.Nullable; +import java.time.Duration; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; + /** * Unit tests for {@link RedisCacheConfiguration}. * @@ -56,6 +60,25 @@ void shouldAllowConverterRegistration() { assertThat(config.getConversionService().canConvert(DomainType.class, String.class)).isTrue(); } + + @Test + void shouldGetDynamicTtlGivenTtlProvider() { + + final int[] base = {1}; + + Supplier ttlProvider = () -> { + base[0] = base[0] * 10; + return Duration.ofSeconds(base[0]); + }; + + RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() + .entryTtlProvider(ttlProvider); + + assertThat(defaultCacheConfiguration.getTtl()).isEqualTo(Duration.ofSeconds(10)); + assertThat(defaultCacheConfiguration.getTtl()).isEqualTo(Duration.ofSeconds(100)); + assertThat(defaultCacheConfiguration.getTtl()).isEqualTo(Duration.ofSeconds(1000)); + } + private static class DomainType { } From 65da058133f0e53b67c6ea4951522cafabc781ea Mon Sep 17 00:00:00 2001 From: i544693 Date: Wed, 24 May 2023 16:43:14 +0800 Subject: [PATCH 3/5] chore: code clean. --- .../data/redis/cache/RedisCacheConfiguration.java | 4 ++-- .../data/redis/cache/RedisCacheConfigurationUnitTests.java | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java b/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java index bd9acc61ec..b16b353fff 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java @@ -212,9 +212,9 @@ public RedisCacheConfiguration entryTtl(Duration ttl) { } /** - * Set the ttl Provider to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache. + * Set the ttl Provider, which can dynamic provide ttl to apply for cache entries. * - * @param ttlProvider must not be {@literal null}, and the ttl should not be {@literal null} + * @param ttlProvider must not be {@literal null}, and the ttl must not be {@literal null} either. * @return new {@link RedisCacheConfiguration}. */ public RedisCacheConfiguration entryTtlProvider(Supplier ttlProvider) { diff --git a/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java b/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java index 614c114b01..ac3410fdc1 100644 --- a/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java @@ -25,7 +25,6 @@ import org.springframework.lang.Nullable; import java.time.Duration; -import java.util.concurrent.ThreadLocalRandom; import java.util.function.Supplier; /** From 88c7fd31f9179700f26a28239e33a9b381aa8729 Mon Sep 17 00:00:00 2001 From: i544693 Date: Fri, 26 May 2023 14:58:03 +0800 Subject: [PATCH 4/5] update: provide some ctx for ttl provider. --- .../data/redis/cache/RedisCache.java | 4 +-- .../redis/cache/RedisCacheConfiguration.java | 29 ++++++++++++------- .../RedisCacheConfigurationUnitTests.java | 16 ++++------ 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCache.java b/src/main/java/org/springframework/data/redis/cache/RedisCache.java index 5c7193b521..4c7fec9c19 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCache.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCache.java @@ -185,7 +185,7 @@ public void put(Object key, @Nullable Object value) { } getCacheWriter().put(getName(), createAndConvertCacheKey(key), serializeCacheValue(cacheValue), - getCacheConfiguration().getTtl()); + getCacheConfiguration().getTtl(key, value)); } @Override @@ -198,7 +198,7 @@ public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { } byte[] result = getCacheWriter().putIfAbsent(getName(), createAndConvertCacheKey(key), - serializeCacheValue(cacheValue), getCacheConfiguration().getTtl()); + serializeCacheValue(cacheValue), getCacheConfiguration().getTtl(key, value)); return result != null ? new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result))) : null; } diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java b/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java index b16b353fff..7b57253a87 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheConfiguration.java @@ -17,8 +17,8 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.function.Supplier; import org.springframework.cache.Cache; import org.springframework.cache.interceptor.SimpleKey; @@ -108,7 +108,7 @@ public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader c registerDefaultConverters(conversionService); - return new RedisCacheConfiguration(() -> Duration.ZERO, DEFAULT_CACHE_NULL_VALUES, DEFAULT_USE_PREFIX, + return new RedisCacheConfiguration((k, v) -> Duration.ZERO, DEFAULT_CACHE_NULL_VALUES, DEFAULT_USE_PREFIX, CacheKeyPrefix.simple(), SerializationPair.fromSerializer(RedisSerializer.string()), SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService); @@ -121,17 +121,17 @@ public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader c private final ConversionService conversionService; - private final Supplier ttlProvider; + private final BiFunction ttlProvider; private final SerializationPair keySerializationPair; private final SerializationPair valueSerializationPair; @SuppressWarnings("unchecked") - private RedisCacheConfiguration(Supplier ttl, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix, + private RedisCacheConfiguration(BiFunction ttlProvider, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix, SerializationPair keySerializationPair, SerializationPair valueSerializationPair, ConversionService conversionService) { - this.ttlProvider = ttl; + this.ttlProvider = ttlProvider; this.cacheNullValues = cacheNullValues; this.usePrefix = usePrefix; this.keyPrefix = keyPrefix; @@ -207,17 +207,18 @@ public RedisCacheConfiguration entryTtl(Duration ttl) { Assert.notNull(ttl, "TTL duration must not be null"); - return new RedisCacheConfiguration(() -> ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, + return new RedisCacheConfiguration((k, v) -> ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair, valueSerializationPair, conversionService); } /** * Set the ttl Provider, which can dynamic provide ttl to apply for cache entries. + * @param ttlProvider {@link BiFunction} calculate ttl with the actual original cache key and value, + * which must not be {@literal null}, and the ttl must not be {@literal null} either. * - * @param ttlProvider must not be {@literal null}, and the ttl must not be {@literal null} either. * @return new {@link RedisCacheConfiguration}. */ - public RedisCacheConfiguration entryTtlProvider(Supplier ttlProvider) { + public RedisCacheConfiguration entryTtlProvider(BiFunction ttlProvider) { Assert.notNull(ttlProvider, "ttlProvider must not be null"); @@ -317,11 +318,19 @@ public SerializationPair getValueSerializationPair() { } /** - * @return The expiration time (ttl) for cache entries. Never {@literal null}. + * @return The constant expiration time (ttl) for cache entries. Never {@literal null}. */ public Duration getTtl() { - Duration ttl = ttlProvider.get(); + Duration ttl = ttlProvider.apply(Object.class, Object.class); + Assert.notNull(ttl, "TTL duration must not be null"); + return ttl; + } + /** + * @return The expiration time (ttl) for cache entries with original key and value. Never {@literal null}. + */ + public Duration getTtl(Object key, Object val) { + Duration ttl = ttlProvider.apply(key, val); Assert.notNull(ttl, "TTL duration must not be null"); return ttl; diff --git a/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java b/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java index ac3410fdc1..7dde2f40a0 100644 --- a/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/redis/cache/RedisCacheConfigurationUnitTests.java @@ -25,7 +25,7 @@ import org.springframework.lang.Nullable; import java.time.Duration; -import java.util.function.Supplier; +import java.util.function.BiFunction; /** * Unit tests for {@link RedisCacheConfiguration}. @@ -63,19 +63,15 @@ void shouldAllowConverterRegistration() { @Test void shouldGetDynamicTtlGivenTtlProvider() { - final int[] base = {1}; - - Supplier ttlProvider = () -> { - base[0] = base[0] * 10; - return Duration.ofSeconds(base[0]); - }; + BiFunction ttlProvider = (key, val) -> + Duration.ofSeconds(Integer.parseInt(key + String.valueOf(val))); RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .entryTtlProvider(ttlProvider); - assertThat(defaultCacheConfiguration.getTtl()).isEqualTo(Duration.ofSeconds(10)); - assertThat(defaultCacheConfiguration.getTtl()).isEqualTo(Duration.ofSeconds(100)); - assertThat(defaultCacheConfiguration.getTtl()).isEqualTo(Duration.ofSeconds(1000)); + assertThat(defaultCacheConfiguration.getTtl(1, 12)).isEqualTo(Duration.ofSeconds(112)); + assertThat(defaultCacheConfiguration.getTtl(15, 22)).isEqualTo(Duration.ofSeconds(1522)); + assertThat(defaultCacheConfiguration.getTtl(77, 0)).isEqualTo(Duration.ofSeconds(770)); } private static class DomainType { From 3641b39be03941e71429c61a06c9f3fd6131d61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Koy=20=5B=27k=C9=94=C9=AA=5D?= Date: Fri, 26 May 2023 15:05:45 +0800 Subject: [PATCH 5/5] chore: code format. --- .../java/org/springframework/data/redis/cache/RedisCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCache.java b/src/main/java/org/springframework/data/redis/cache/RedisCache.java index 4c7fec9c19..69967eee44 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCache.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCache.java @@ -185,7 +185,7 @@ public void put(Object key, @Nullable Object value) { } getCacheWriter().put(getName(), createAndConvertCacheKey(key), serializeCacheValue(cacheValue), - getCacheConfiguration().getTtl(key, value)); + getCacheConfiguration().getTtl(key, value)); } @Override @@ -198,7 +198,7 @@ public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { } byte[] result = getCacheWriter().putIfAbsent(getName(), createAndConvertCacheKey(key), - serializeCacheValue(cacheValue), getCacheConfiguration().getTtl(key, value)); + serializeCacheValue(cacheValue), getCacheConfiguration().getTtl(key, value)); return result != null ? new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result))) : null; }