Polishing.

Replace blocking lock wait with non-blocking flow. Add support for asynchronous storage to persist the cache value after retrieval from the value supplier. Introduce AsyncCacheWriter abstraction to improve functional guards.

Reformat code. Remove redundant tests. Revisit deprecation notices with consistent mention of the version in which the deprecation was introduced.

Refine exception messages when RedisCache does not support async retrieval.

See #2650
Original pull request: #2717
This commit is contained in:
Mark Paluch
2023-10-10 13:51:53 +02:00
parent a72c4268a3
commit efc9ecf695
10 changed files with 731 additions and 828 deletions

View File

@@ -106,6 +106,7 @@ public class RedisRuntimeHints implements RuntimeHintsRegistrar {
TypeReference.of(ReactiveClusterScriptingCommands.class),
TypeReference.of(ReactiveClusterGeoCommands.class),
TypeReference.of(ReactiveClusterHyperLogLogCommands.class), TypeReference.of(ReactiveRedisOperations.class),
TypeReference.of(ReactiveRedisConnectionFactory.class),
TypeReference.of(ReactiveRedisTemplate.class), TypeReference.of(RedisOperations.class),
TypeReference.of(RedisTemplate.class), TypeReference.of(StringRedisTemplate.class),
TypeReference.of(KeyspaceConfiguration.class), TypeReference.of(MappingConfiguration.class),

View File

@@ -15,18 +15,21 @@
*/
package org.springframework.data.redis.cache;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.connection.ReactiveStringCommands;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
@@ -34,9 +37,8 @@ import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* {@link RedisCacheWriter} implementation capable of reading/writing binary data from/to Redis in {@literal standalone}
@@ -44,8 +46,8 @@ import reactor.core.publisher.Mono;
* {@link RedisConnection}.
* <p>
* {@link DefaultRedisCacheWriter} can be used in
* {@link RedisCacheWriter#lockingRedisCacheWriter(RedisConnectionFactory) locking}
* or {@link RedisCacheWriter#nonLockingRedisCacheWriter(RedisConnectionFactory) non-locking} mode. While
* {@link RedisCacheWriter#lockingRedisCacheWriter(RedisConnectionFactory) locking} or
* {@link RedisCacheWriter#nonLockingRedisCacheWriter(RedisConnectionFactory) non-locking} mode. While
* {@literal non-locking} aims for maximum performance it may result in overlapping, non-atomic, command execution for
* operations spanning multiple Redis interactions like {@code putIfAbsent}. The {@literal locking} counterpart prevents
* command overlap by setting an explicit lock key and checking against presence of this key which leads to additional
@@ -59,6 +61,9 @@ import reactor.core.publisher.Mono;
*/
class DefaultRedisCacheWriter implements RedisCacheWriter {
private static final boolean REACTIVE_REDIS_CONNECTION_FACTORY_PRESENT = ClassUtils
.isPresent("org.springframework.data.redis.connection.ReactiveRedisConnectionFactory", null);
private final BatchStrategy batchStrategy;
private final CacheStatisticsCollector statistics;
@@ -69,6 +74,8 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
private final TtlFunction lockTtl;
private final AsyncCacheWriter asyncCacheWriter;
/**
* @param connectionFactory must not be {@literal null}.
* @param batchStrategy must not be {@literal null}.
@@ -109,6 +116,12 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
this.lockTtl = lockTtl;
this.statistics = cacheStatisticsCollector;
this.batchStrategy = batchStrategy;
if (REACTIVE_REDIS_CONNECTION_FACTORY_PRESENT && this.connectionFactory instanceof ReactiveRedisConnectionFactory) {
asyncCacheWriter = new AsynchronousCacheWriterDelegate();
} else {
asyncCacheWriter = UnsupportedAsyncCacheWriter.INSTANCE;
}
}
@Override
@@ -138,8 +151,8 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
}
@Override
public boolean isRetrieveSupported() {
return isReactive();
public boolean supportsAsyncRetrieve() {
return asyncCacheWriter.isSupported();
}
@Override
@@ -148,68 +161,19 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
Assert.notNull(name, "Name must not be null");
Assert.notNull(key, "Key must not be null");
CompletableFuture<byte[]> result = nonBlockingRetrieveFunction(name).apply(key, ttl);
return asyncCacheWriter.retrieve(name, key, ttl) //
.thenApply(cachedValue -> {
result = result.thenApply(cachedValue -> {
statistics.incGets(name);
statistics.incGets(name);
if (cachedValue != null) {
statistics.incHits(name);
} else {
statistics.incMisses(name);
}
if (cachedValue != null) {
statistics.incHits(name);
} else {
statistics.incMisses(name);
}
return cachedValue;
});
return result;
}
private BiFunction<byte[], Duration, CompletableFuture<byte[]>> nonBlockingRetrieveFunction(String cacheName) {
return isReactive() ? reactiveRetrieveFunction(cacheName) : asyncRetrieveFunction(cacheName);
}
// TODO: Possibly remove if we rely on the default Cache.retrieve(..) behavior
// after assessing RedisCacheWriter.isRetrieveSupported().
// Function applied for Cache.retrieve(key) when a non-reactive Redis driver is used, such as Jedis.
private BiFunction<byte[], Duration, CompletableFuture<byte[]>> asyncRetrieveFunction(String cacheName) {
return (key, ttl) -> {
Supplier<byte[]> getKey = () -> execute(cacheName, connection -> connection.stringCommands().get(key));
Supplier<byte[]> getKeyWithExpiration = () -> execute(cacheName, connection ->
connection.stringCommands().getEx(key, Expiration.from(ttl)));
return shouldExpireWithin(ttl)
? CompletableFuture.supplyAsync(getKeyWithExpiration)
: CompletableFuture.supplyAsync(getKey);
};
}
// Function applied for Cache.retrieve(key) when a reactive Redis driver is used, such as Lettuce.
private BiFunction<byte[], Duration, CompletableFuture<byte[]>> reactiveRetrieveFunction(String cacheName) {
return (key, ttl) -> {
ByteBuffer wrappedKey = ByteBuffer.wrap(key);
Flux<?> cacheLockCheckFlux = Flux.interval(Duration.ZERO, this.sleepTime).takeUntil(count ->
executeLockFree(connection -> !doCheckLock(cacheName, connection)));
Mono<ByteBuffer> getMono = shouldExpireWithin(ttl)
? executeReactively(connection -> connection.stringCommands().getEx(wrappedKey, Expiration.from(ttl)))
: executeReactively(connection -> connection.stringCommands().get(wrappedKey));
Mono<ByteBuffer> result = cacheLockCheckFlux.then(getMono);
@SuppressWarnings("all")
Mono<byte[]> byteArrayResult = result.map(DefaultRedisCacheWriter::nullSafeGetBytes);
return byteArrayResult.toFuture();
};
return cachedValue;
});
}
@Override
@@ -222,8 +186,8 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
execute(name, connection -> {
if (shouldExpireWithin(ttl)) {
connection.stringCommands()
.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
connection.stringCommands().set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS),
SetOption.upsert());
} else {
connection.stringCommands().set(key, value);
}
@@ -234,6 +198,17 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
statistics.incPuts(name);
}
@Override
public CompletableFuture<Void> store(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(key, "Key must not be null");
Assert.notNull(value, "Value must not be null");
return asyncCacheWriter.store(name, key, value, ttl) //
.thenRun(() -> statistics.incPuts(name));
}
@Override
public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
@@ -252,9 +227,10 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
boolean put;
if (shouldExpireWithin(ttl)) {
put = isTrue(connection.stringCommands().set(key, value, Expiration.from(ttl), SetOption.ifAbsent()));
put = ObjectUtils.nullSafeEquals(
connection.stringCommands().set(key, value, Expiration.from(ttl), SetOption.ifAbsent()), true);
} else {
put = isTrue(connection.stringCommands().setNX(key, value));
put = ObjectUtils.nullSafeEquals(connection.stringCommands().setNX(key, value), true);
}
if (put) {
@@ -348,8 +324,7 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
Expiration expiration = Expiration.from(this.lockTtl.getTimeToLive(contextualKey, contextualValue));
return connection.stringCommands()
.set(createCacheLockKey(name), new byte[0], expiration, SetOption.SET_IF_ABSENT);
return connection.stringCommands().set(createCacheLockKey(name), new byte[0], expiration, SetOption.SET_IF_ABSENT);
}
/**
@@ -381,18 +356,6 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
}
}
private <T> T executeReactively(Function<ReactiveRedisConnection, T> callback) {
ReactiveRedisConnection connection = getReactiveRedisConnectionFactory().getReactiveConnection();
try {
return callback.apply(connection);
}
finally {
connection.closeLater();
}
}
/**
* Determines whether this {@link RedisCacheWriter} uses locks during caching operations.
*
@@ -419,40 +382,191 @@ class DefaultRedisCacheWriter implements RedisCacheWriter {
// Re-interrupt current Thread to allow other participants to react.
Thread.currentThread().interrupt();
String message = String.format("Interrupted while waiting to unlock cache %s", name);
throw new PessimisticLockingFailureException(message, cause);
throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name),
cause);
} finally {
this.statistics.incLockTime(name, System.nanoTime() - lockWaitTimeNs);
}
}
boolean doCheckLock(String name, RedisConnection connection) {
return isTrue(connection.keyCommands().exists(createCacheLockKey(name)));
return ObjectUtils.nullSafeEquals(connection.keyCommands().exists(createCacheLockKey(name)), true);
}
private boolean isReactive() {
return this.connectionFactory instanceof ReactiveRedisConnectionFactory;
}
private ReactiveRedisConnectionFactory getReactiveRedisConnectionFactory() {
return (ReactiveRedisConnectionFactory) this.connectionFactory;
}
private static byte[] createCacheLockKey(String name) {
byte[] createCacheLockKey(String name) {
return (name + "~lock").getBytes(StandardCharsets.UTF_8);
}
private static boolean isTrue(@Nullable Boolean value) {
return Boolean.TRUE.equals(value);
}
@Nullable
private static byte[] nullSafeGetBytes(@Nullable ByteBuffer value) {
return value != null ? ByteUtils.getBytes(value) : null;
}
private static boolean shouldExpireWithin(@Nullable Duration ttl) {
return ttl != null && !ttl.isZero() && !ttl.isNegative();
}
/**
* Interface for asynchronous cache retrieval.
*
* @since 3.2
*/
interface AsyncCacheWriter {
/**
* @return {@code true} if async cache operations are supported; {@code false} otherwise.
*/
boolean isSupported();
/**
* Retrieve a cache entry asynchronously.
*
* @param name the cache name from which to retrieve the cache entry.
* @param key the cache entry key.
* @param ttl optional TTL to set for Time-to-Idle eviction.
* @return a future that completes either with a value if the value exists or completing with {@code null} if the
* cache does not contain an entry.
*/
CompletableFuture<byte[]> retrieve(String name, byte[] key, @Nullable Duration ttl);
/**
* Store a cache entry asynchronously.
*
* @param name the cache name which to store the cache entry to.
* @param key the key for the cache entry. Must not be {@literal null}.
* @param value the value stored for the key. Must not be {@literal null}.
* @param ttl optional expiration time. Can be {@literal null}.
* @return a future that signals completion.
*/
CompletableFuture<Void> store(String name, byte[] key, byte[] value, @Nullable Duration ttl);
}
/**
* Unsupported variant of a {@link AsyncCacheWriter}.
*
* @since 3.2
*/
enum UnsupportedAsyncCacheWriter implements AsyncCacheWriter {
INSTANCE;
@Override
public boolean isSupported() {
return false;
}
@Override
public CompletableFuture<byte[]> retrieve(String name, byte[] key, @Nullable Duration ttl) {
throw new UnsupportedOperationException("async retrieve not supported");
}
@Override
public CompletableFuture<Void> store(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
throw new UnsupportedOperationException("async store not supported");
}
}
/**
* Delegate implementing {@link AsyncCacheWriter} to provide asynchronous cache retrieval and storage operations using
* {@link ReactiveRedisConnectionFactory}.
*
* @since 3.2
*/
class AsynchronousCacheWriterDelegate implements AsyncCacheWriter {
@Override
public boolean isSupported() {
return true;
}
@Override
public CompletableFuture<byte[]> retrieve(String name, byte[] key, @Nullable Duration ttl) {
return doWithConnection(connection -> {
ByteBuffer wrappedKey = ByteBuffer.wrap(key);
Mono<?> cacheLockCheck = isLockingCacheWriter() ? waitForLock(connection, name) : Mono.empty();
ReactiveStringCommands stringCommands = connection.stringCommands();
Mono<ByteBuffer> get = shouldExpireWithin(ttl)
? stringCommands.getEx(wrappedKey, Expiration.from(ttl))
: stringCommands.get(wrappedKey);
return cacheLockCheck.then(get).map(ByteUtils::getBytes).toFuture();
});
}
@Override
public CompletableFuture<Void> store(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
return doWithConnection(connection -> {
Mono<?> mono = isLockingCacheWriter()
? doStoreWithLocking(name, key, value, ttl, connection)
: doStore(key, value, ttl, connection);
return mono.then().toFuture();
});
}
private Mono<Boolean> doStoreWithLocking(String name, byte[] key, byte[] value, @Nullable Duration ttl,
ReactiveRedisConnection connection) {
return Mono.usingWhen(doLock(name, key, value, connection), unused -> doStore(key, value, ttl, connection),
unused -> doUnlock(name, connection));
}
private Mono<Boolean> doStore(byte[] cacheKey, byte[] value, @Nullable Duration ttl,
ReactiveRedisConnection connection) {
ByteBuffer wrappedKey = ByteBuffer.wrap(cacheKey);
ByteBuffer wrappedValue = ByteBuffer.wrap(value);
if (shouldExpireWithin(ttl)) {
return connection.stringCommands().set(wrappedKey, wrappedValue,
Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
} else {
return connection.stringCommands().set(wrappedKey, wrappedValue);
}
}
private Mono<Object> doLock(String name, Object contextualKey, @Nullable Object contextualValue,
ReactiveRedisConnection connection) {
ByteBuffer key = ByteBuffer.wrap(createCacheLockKey(name));
ByteBuffer value = ByteBuffer.wrap(new byte[0]);
Expiration expiration = Expiration.from(lockTtl.getTimeToLive(contextualKey, contextualValue));
return connection.stringCommands().set(key, value, expiration, SetOption.SET_IF_ABSENT) //
// Ensure we emit an object, otherwise, the Mono.usingWhen operator doesn't run the inner resource function.
.thenReturn(Boolean.TRUE);
}
private Mono<Void> doUnlock(String name, ReactiveRedisConnection connection) {
return connection.keyCommands().del(ByteBuffer.wrap(createCacheLockKey(name))).then();
}
private Mono<Void> waitForLock(ReactiveRedisConnection connection, String cacheName) {
AtomicLong lockWaitTimeNs = new AtomicLong();
byte[] cacheLockKey = createCacheLockKey(cacheName);
Flux<Long> wait = Flux.interval(Duration.ZERO, sleepTime);
Mono<Boolean> exists = connection.keyCommands().exists(ByteBuffer.wrap(cacheLockKey)).filter(it -> !it);
return wait.doOnSubscribe(subscription -> lockWaitTimeNs.set(System.nanoTime())) //
.flatMap(it -> exists) //
.doFinally(signalType -> statistics.incLockTime(cacheName, System.nanoTime() - lockWaitTimeNs.get())) //
.next() //
.then();
}
private <T> CompletableFuture<T> doWithConnection(
Function<ReactiveRedisConnection, CompletableFuture<T>> callback) {
ReactiveRedisConnectionFactory cf = (ReactiveRedisConnectionFactory) connectionFactory;
return Mono.usingWhen(Mono.fromSupplier(cf::getReactiveConnection), //
it -> Mono.fromCompletionStage(callback.apply(it)), //
ReactiveRedisConnection::closeLater) //
.toFuture();
}
}
}

View File

@@ -55,7 +55,6 @@ import org.springframework.util.ReflectionUtils;
* @author Piotr Mionskowski
* @author Jos Roseboom
* @author John Blum
* @see org.springframework.cache.support.AbstractValueAdaptingCache
* @since 2.0
*/
@SuppressWarnings("unused")
@@ -72,16 +71,16 @@ public class RedisCache extends AbstractValueAdaptingCache {
private final String name;
/**
* Create a new {@link RedisCache} with the given {@link String name} and {@link RedisCacheConfiguration},
* using the {@link RedisCacheWriter} to execute Redis commands supporting the cache operations.
* Create a new {@link RedisCache} with the given {@link String name} and {@link RedisCacheConfiguration}, using the
* {@link RedisCacheWriter} to execute Redis commands supporting the cache operations.
*
* @param name {@link String name} for this {@link Cache}; must not be {@literal null}.
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by
* executing the necessary Redis commands; must not be {@literal null}.
* @param cacheConfiguration {@link RedisCacheConfiguration} applied to this {@link RedisCache} on creation;
* must not be {@literal null}.
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by executing the
* necessary Redis commands; must not be {@literal null}.
* @param cacheConfiguration {@link RedisCacheConfiguration} applied to this {@link RedisCache} on creation; must not
* be {@literal null}.
* @throws IllegalArgumentException if either the given {@link RedisCacheWriter} or {@link RedisCacheConfiguration}
* are {@literal null} or the given {@link String} name for this {@link RedisCache} is {@literal null}.
* are {@literal null} or the given {@link String} name for this {@link RedisCache} is {@literal null}.
*/
protected RedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfiguration) {
@@ -120,7 +119,7 @@ public class RedisCache extends AbstractValueAdaptingCache {
* accessing entries in the cache.
*
* @return the configured {@link ConversionService} used to convert {@link Object cache keys} to a {@link String} when
* accessing entries in the cache.
* accessing entries in the cache.
* @see RedisCacheConfiguration#getConversionService()
* @see #getCacheConfiguration()
*/
@@ -218,16 +217,7 @@ public class RedisCache extends AbstractValueAdaptingCache {
@Override
public void put(Object key, @Nullable Object value) {
Object cacheValue = preProcessCacheValue(value);
if (nullCacheValueIsNotAllowed(cacheValue)) {
String message = String.format("Cache '%s' does not allow 'null' values; Avoid storing null"
+ " via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null'"
+ " via RedisCacheConfiguration", getName());
throw new IllegalArgumentException(message);
}
Object cacheValue = processAndCheckValue(value);
getCacheWriter().put(getName(), createAndConvertCacheKey(key), serializeCacheValue(cacheValue),
getTimeToLive(key, value));
@@ -279,6 +269,61 @@ public class RedisCache extends AbstractValueAdaptingCache {
getCacheWriter().remove(getName(), createAndConvertCacheKey(key));
}
@Override
public CompletableFuture<?> retrieve(Object key) {
if (!getCacheWriter().supportsAsyncRetrieve()) {
throw new UnsupportedOperationException(
"The Redis driver configured with RedisCache through RedisCacheWriter does not support CompletableFuture-based retrieval");
}
return retrieveValue(key).thenApply(this::nullSafeDeserializedStoreValue);
}
@Override
@SuppressWarnings("unchecked")
public <T> CompletableFuture<T> retrieve(Object key, Supplier<CompletableFuture<T>> valueLoader) {
if (!getCacheWriter().supportsAsyncRetrieve()) {
throw new UnsupportedOperationException(
"The Redis driver configured with RedisCache through RedisCacheWriter does not support CompletableFuture-based retrieval");
}
return retrieveValue(key) //
.thenCompose(bytes -> {
if (bytes != null) {
return CompletableFuture.completedFuture((T) nullSafeDeserializedStoreValue(bytes));
}
return valueLoader.get().thenCompose(value -> {
Object cacheValue = processAndCheckValue(value);
return getCacheWriter()
.store(getName(), createAndConvertCacheKey(key), serializeCacheValue(cacheValue),
getTimeToLive(key, cacheValue)) //
.thenApply(v -> value);
});
});
}
private Object processAndCheckValue(@Nullable Object value) {
Object cacheValue = preProcessCacheValue(value);
if (nullCacheValueIsNotAllowed(cacheValue)) {
String message = String.format("Cache '%s' does not allow 'null' values; Avoid storing null"
+ " via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null'"
+ " via RedisCacheConfiguration", getName());
throw new IllegalArgumentException(message);
}
return cacheValue;
}
/**
* Customization hook called before passing object to
* {@link org.springframework.data.redis.serializer.RedisSerializer}.
@@ -291,40 +336,6 @@ public class RedisCache extends AbstractValueAdaptingCache {
return value != null ? value : isAllowNullValues() ? NullValue.INSTANCE : null;
}
@Override
public CompletableFuture<?> retrieve(Object key) {
if (getCacheWriter().isRetrieveSupported()) {
return retrieveValue(key).thenApply(this::nullSafeDeserializedStoreValue);
}
return super.retrieve(key);
}
@Override
@SuppressWarnings("unchecked")
public <T> CompletableFuture<T> retrieve(Object key, Supplier<CompletableFuture<T>> valueLoader) {
if (getCacheWriter().isRetrieveSupported()) {
return retrieveValue(key)
.thenApply(this::nullSafeDeserializedStoreValue)
.thenCompose(cachedValue -> cachedValue != null
? CompletableFuture.completedFuture((T) cachedValue)
: valueLoader.get());
}
return super.retrieve(key, valueLoader);
}
CompletableFuture<byte[]> retrieveValue(Object key) {
return getCacheWriter().retrieve(getName(), createAndConvertCacheKey(key));
}
@Nullable
Object nullSafeDeserializedStoreValue(@Nullable byte[] value) {
return value != null ? fromStoreValue(deserializeCacheValue(value)) : null;
}
/**
* Serialize the given {@link String cache key}.
*
@@ -357,7 +368,7 @@ public class RedisCache extends AbstractValueAdaptingCache {
*
* @param value array of bytes to deserialize; must not be {@literal null}.
* @return an {@link Object} deserialized from the array of bytes using the configured value
* {@link RedisSerializationContext.SerializationPair}; can be {@literal null}.
* {@link RedisSerializationContext.SerializationPair}; can be {@literal null}.
* @see RedisCacheConfiguration#getValueSerializationPair()
*/
@Nullable
@@ -418,13 +429,23 @@ public class RedisCache extends AbstractValueAdaptingCache {
return key.toString();
}
String message = String.format("Cannot convert cache key %s to String; Please register a suitable Converter"
+ " via 'RedisCacheConfiguration.configureKeyConverters(...)' or override '%s.toString()'",
String message = String.format(
"Cannot convert cache key %s to String; Please register a suitable Converter"
+ " via 'RedisCacheConfiguration.configureKeyConverters(...)' or override '%s.toString()'",
source, key.getClass().getName());
throw new IllegalStateException(message);
}
private CompletableFuture<byte[]> retrieveValue(Object key) {
return getCacheWriter().retrieve(getName(), createAndConvertCacheKey(key));
}
@Nullable
private Object nullSafeDeserializedStoreValue(@Nullable byte[] value) {
return value != null ? fromStoreValue(deserializeCacheValue(value)) : null;
}
private boolean hasToStringMethod(Object target) {
return hasToStringMethod(target.getClass());
}

View File

@@ -34,108 +34,29 @@ import org.springframework.util.Assert;
/**
* {@link CacheManager} implementation for Redis backed by {@link RedisCache}.
* <p>
* This {@link CacheManager} creates {@link Cache caches} on first write, by default. Empty {@link Cache caches}
* are not visible in Redis due to how Redis represents empty data structures.
* This {@link CacheManager} creates {@link Cache caches} on first write, by default. Empty {@link Cache caches} are not
* visible in Redis due to how Redis represents empty data structures.
* <p>
* {@link Cache Caches} requiring a different {@link RedisCacheConfiguration cache configuration}
* than the {@link RedisCacheConfiguration#defaultCacheConfig() default cache configuration}
* can be specified via {@link RedisCacheManagerBuilder#withInitialCacheConfigurations(Map)} or individually
* using {@link RedisCacheManagerBuilder#withCacheConfiguration(String, RedisCacheConfiguration)}.
* {@link Cache Caches} requiring a different {@link RedisCacheConfiguration cache configuration} than the
* {@link RedisCacheConfiguration#defaultCacheConfig() default cache configuration} can be specified via
* {@link RedisCacheManagerBuilder#withInitialCacheConfigurations(Map)} or individually using
* {@link RedisCacheManagerBuilder#withCacheConfiguration(String, RedisCacheConfiguration)}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Yanming Zhou
* @author John Blum
* @see org.springframework.cache.Cache
* @see RedisCache
* @see RedisCacheConfiguration
* @see RedisCacheWriter
* @see org.springframework.cache.CacheManager
* @see org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager
* @see org.springframework.data.redis.connection.RedisConnectionFactory
* @see org.springframework.data.redis.cache.RedisCacheConfiguration
* @see org.springframework.data.redis.cache.RedisCacheWriter
* @since 2.0
*/
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
protected static final boolean DEFAULT_ALLOW_RUNTIME_CACHE_CREATION = true;
/**
* Factory method returning a {@literal Builder} used to construct and configure a {@link RedisCacheManager}.
*
* @return new {@link RedisCacheManagerBuilder}.
* @since 2.3
*/
public static RedisCacheManagerBuilder builder() {
return new RedisCacheManagerBuilder();
}
/**
* Factory method returning a {@literal Builder} used to construct and configure a {@link RedisCacheManager}
* initialized with the given {@link RedisCacheWriter}.
*
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations
* by executing appropriate Redis commands; must not be {@literal null}.
* @return new {@link RedisCacheManagerBuilder}.
* @throws IllegalArgumentException if the given {@link RedisCacheWriter} is {@literal null}.
* @see org.springframework.data.redis.cache.RedisCacheWriter
*/
public static RedisCacheManagerBuilder builder(RedisCacheWriter cacheWriter) {
Assert.notNull(cacheWriter, "CacheWriter must not be null");
return RedisCacheManagerBuilder.fromCacheWriter(cacheWriter);
}
/**
* Factory method returning a {@literal Builder} used to construct and configure a {@link RedisCacheManager}
* initialized with the given {@link RedisConnectionFactory}.
*
* @param connectionFactory {@link RedisConnectionFactory} used by the {@link RedisCacheManager}
* to acquire connections to Redis when performing {@link RedisCache} operations; must not be {@literal null}.
* @return new {@link RedisCacheManagerBuilder}.
* @throws IllegalArgumentException if the given {@link RedisConnectionFactory} is {@literal null}.
* @see org.springframework.data.redis.connection.RedisConnectionFactory
*/
public static RedisCacheManagerBuilder builder(RedisConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
return RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory);
}
/**
* Factory method used to construct a new {@link RedisCacheManager} initialized with
* the given {@link RedisConnectionFactory} and using {@link RedisCacheConfiguration#defaultCacheConfig() defaults}
* for caching.
*
* <dl>
* <dt>locking</dt>
* <dd>disabled</dd>
* <dt>batch strategy</dt>
* <dd>{@link BatchStrategies#keys()}</dd>
* <dt>cache configuration</dt>
* <dd>{@link RedisCacheConfiguration#defaultCacheConfig()}</dd>
* <dt>initial caches</dt>
* <dd>none</dd>
* <dt>transaction aware</dt>
* <dd>no</dd>
* <dt>in-flight cache creation</dt>
* <dd>enabled</dd>
* </dl>
*
* @param connectionFactory {@link RedisConnectionFactory} used by the {@link RedisCacheManager}
* to acquire connections to Redis when performing {@link RedisCache} operations; must not be {@literal null}.
* @return new {@link RedisCacheManager}.
* @throws IllegalArgumentException if the given {@link RedisConnectionFactory} is {@literal null}.
* @see org.springframework.data.redis.connection.RedisConnectionFactory
*/
public static RedisCacheManager create(RedisConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
return new RedisCacheManager(org.springframework.data.redis.cache.RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory),
RedisCacheConfiguration.defaultCacheConfig());
}
private final boolean allowRuntimeCacheCreation;
private final RedisCacheConfiguration defaultCacheConfiguration;
@@ -145,17 +66,17 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
/**
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter}
* and a default {@link RedisCacheConfiguration}.
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter} and a default
* {@link RedisCacheConfiguration}.
* <p>
* Allows {@link RedisCache cache} creation at runtime.
*
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations
* by executing appropriate Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches}
* by default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by executing appropriate
* Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches} by
* default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @throws IllegalArgumentException if either the given {@link RedisCacheWriter} or {@link RedisCacheConfiguration}
* are {@literal null}.
* are {@literal null}.
* @see org.springframework.data.redis.cache.RedisCacheConfiguration
* @see org.springframework.data.redis.cache.RedisCacheWriter
*/
@@ -164,17 +85,17 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter}
* and default {@link RedisCacheConfiguration}, and whether to allow cache creation at runtime.
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter} and default
* {@link RedisCacheConfiguration}, and whether to allow cache creation at runtime.
*
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations
* by executing appropriate Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches}
* by default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by executing appropriate
* Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches} by
* default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param allowRuntimeCacheCreation boolean specifying whether to allow creation of undeclared caches at runtime;
* {@literal true} by default. Maybe just use {@link RedisCacheConfiguration#defaultCacheConfig()}.
* {@literal true} by default. Maybe just use {@link RedisCacheConfiguration#defaultCacheConfig()}.
* @throws IllegalArgumentException if either the given {@link RedisCacheWriter} or {@link RedisCacheConfiguration}
* are {@literal null}.
* are {@literal null}.
* @see org.springframework.data.redis.cache.RedisCacheConfiguration
* @see org.springframework.data.redis.cache.RedisCacheWriter
* @since 2.0.4
@@ -191,20 +112,20 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter}
* and a default {@link RedisCacheConfiguration}, along with an optional, initial set of {@link String cache names}
* used to create {@link RedisCache Redis caches} on startup.
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter} and a default
* {@link RedisCacheConfiguration}, along with an optional, initial set of {@link String cache names} used to create
* {@link RedisCache Redis caches} on startup.
* <p>
* Allows {@link RedisCache cache} creation at runtime.
*
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations
* by executing appropriate Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches}
* by default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by executing appropriate
* Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches} by
* default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param initialCacheNames optional set of {@link String cache names} used to create {@link RedisCache Redis caches}
* on startup. The default {@link RedisCacheConfiguration} will be applied to each cache.
* on startup. The default {@link RedisCacheConfiguration} will be applied to each cache.
* @throws IllegalArgumentException if either the given {@link RedisCacheWriter} or {@link RedisCacheConfiguration}
* are {@literal null}.
* are {@literal null}.
* @see org.springframework.data.redis.cache.RedisCacheConfiguration
* @see org.springframework.data.redis.cache.RedisCacheWriter
*/
@@ -215,22 +136,22 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter}
* and default {@link RedisCacheConfiguration}, and whether to allow cache creation at runtime.
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter} and default
* {@link RedisCacheConfiguration}, and whether to allow cache creation at runtime.
* <p>
* Additionally, the optional, initial set of {@link String cache names} will be used to create
* {@link RedisCache Redis caches} on startup.
* Additionally, the optional, initial set of {@link String cache names} will be used to create {@link RedisCache
* Redis caches} on startup.
*
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations
* by executing appropriate Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches}
* by default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by executing appropriate
* Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches} by
* default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param allowRuntimeCacheCreation boolean specifying whether to allow creation of undeclared caches at runtime;
* {@literal true} by default. Maybe just use {@link RedisCacheConfiguration#defaultCacheConfig()}.
* {@literal true} by default. Maybe just use {@link RedisCacheConfiguration#defaultCacheConfig()}.
* @param initialCacheNames optional set of {@link String cache names} used to create {@link RedisCache Redis caches}
* on startup. The default {@link RedisCacheConfiguration} will be applied to each cache.
* on startup. The default {@link RedisCacheConfiguration} will be applied to each cache.
* @throws IllegalArgumentException if either the given {@link RedisCacheWriter} or {@link RedisCacheConfiguration}
* are {@literal null}.
* are {@literal null}.
* @see org.springframework.data.redis.cache.RedisCacheConfiguration
* @see org.springframework.data.redis.cache.RedisCacheWriter
* @since 2.0.4
@@ -254,15 +175,15 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
* <p>
* Allows {@link RedisCache cache} creation at runtime.
*
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations
* by executing appropriate Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches}
* by default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by executing appropriate
* Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches} by
* default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param initialCacheConfigurations {@link Map} of declared, known {@link String cache names} along with associated
* {@link RedisCacheConfiguration} used to create and configure {@link RedisCache Reds caches} on startup;
* must not be {@literal null}.
* {@link RedisCacheConfiguration} used to create and configure {@link RedisCache Reds caches} on startup;
* must not be {@literal null}.
* @throws IllegalArgumentException if either the given {@link RedisCacheWriter} or {@link RedisCacheConfiguration}
* are {@literal null}.
* are {@literal null}.
* @see org.springframework.data.redis.cache.RedisCacheConfiguration
* @see org.springframework.data.redis.cache.RedisCacheWriter
*/
@@ -273,23 +194,23 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter}
* and a default {@link RedisCacheConfiguration}, and whether to allow {@link RedisCache} creation at runtime.
* Creates a new {@link RedisCacheManager} initialized with the given {@link RedisCacheWriter} and a default
* {@link RedisCacheConfiguration}, and whether to allow {@link RedisCache} creation at runtime.
* <p>
* Additionally, an initial {@link RedisCache} will be created and configured using the associated
* {@link RedisCacheConfiguration} for each {@link String named} {@link RedisCache} in the given {@link Map}.
*
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations
* by executing appropriate Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches}
* by default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by executing appropriate
* Redis commands; must not be {@literal null}.
* @param defaultCacheConfiguration {@link RedisCacheConfiguration} applied to new {@link RedisCache Redis caches} by
* default when no cache-specific {@link RedisCacheConfiguration} is provided; must not be {@literal null}.
* @param allowRuntimeCacheCreation boolean specifying whether to allow creation of undeclared caches at runtime;
* {@literal true} by default. Maybe just use {@link RedisCacheConfiguration#defaultCacheConfig()}.
* @param initialCacheConfigurations {@link Map} of declared, known {@link String cache names} along with
* the associated {@link RedisCacheConfiguration} used to create and configure {@link RedisCache Redis caches}
* on startup; must not be {@literal null}.
* {@literal true} by default. Maybe just use {@link RedisCacheConfiguration#defaultCacheConfig()}.
* @param initialCacheConfigurations {@link Map} of declared, known {@link String cache names} along with the
* associated {@link RedisCacheConfiguration} used to create and configure {@link RedisCache Redis caches} on
* startup; must not be {@literal null}.
* @throws IllegalArgumentException if either the given {@link RedisCacheWriter} or {@link RedisCacheConfiguration}
* are {@literal null}.
* are {@literal null}.
* @see org.springframework.data.redis.cache.RedisCacheConfiguration
* @see org.springframework.data.redis.cache.RedisCacheWriter
* @since 2.0.4
@@ -305,16 +226,93 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* @deprecated use {@link org.springframework.data.redis.cache.RedisCacheManager#RedisCacheManager(RedisCacheWriter, RedisCacheConfiguration, boolean, Map)}
* instead.
* @deprecated since 3.2. Use
* {@link RedisCacheManager#RedisCacheManager(RedisCacheWriter, RedisCacheConfiguration, boolean, Map)}
* instead.
*/
@Deprecated
@Deprecated(since = "3.2")
public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowRuntimeCacheCreation) {
this(cacheWriter, defaultCacheConfiguration, allowRuntimeCacheCreation, initialCacheConfigurations);
}
/**
* Factory method returning a {@literal Builder} used to construct and configure a {@link RedisCacheManager}.
*
* @return new {@link RedisCacheManagerBuilder}.
* @since 2.3
*/
public static RedisCacheManagerBuilder builder() {
return new RedisCacheManagerBuilder();
}
/**
* Factory method returning a {@literal Builder} used to construct and configure a {@link RedisCacheManager}
* initialized with the given {@link RedisCacheWriter}.
*
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by executing appropriate
* Redis commands; must not be {@literal null}.
* @return new {@link RedisCacheManagerBuilder}.
* @throws IllegalArgumentException if the given {@link RedisCacheWriter} is {@literal null}.
* @see org.springframework.data.redis.cache.RedisCacheWriter
*/
public static RedisCacheManagerBuilder builder(RedisCacheWriter cacheWriter) {
Assert.notNull(cacheWriter, "CacheWriter must not be null");
return RedisCacheManagerBuilder.fromCacheWriter(cacheWriter);
}
/**
* Factory method returning a {@literal Builder} used to construct and configure a {@link RedisCacheManager}
* initialized with the given {@link RedisConnectionFactory}.
*
* @param connectionFactory {@link RedisConnectionFactory} used by the {@link RedisCacheManager} to acquire
* connections to Redis when performing {@link RedisCache} operations; must not be {@literal null}.
* @return new {@link RedisCacheManagerBuilder}.
* @throws IllegalArgumentException if the given {@link RedisConnectionFactory} is {@literal null}.
* @see org.springframework.data.redis.connection.RedisConnectionFactory
*/
public static RedisCacheManagerBuilder builder(RedisConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
return RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory);
}
/**
* Factory method used to construct a new {@link RedisCacheManager} initialized with the given
* {@link RedisConnectionFactory} and using {@link RedisCacheConfiguration#defaultCacheConfig() defaults} for caching.
* <dl>
* <dt>locking</dt>
* <dd>disabled</dd>
* <dt>batch strategy</dt>
* <dd>{@link BatchStrategies#keys()}</dd>
* <dt>cache configuration</dt>
* <dd>{@link RedisCacheConfiguration#defaultCacheConfig()}</dd>
* <dt>initial caches</dt>
* <dd>none</dd>
* <dt>transaction aware</dt>
* <dd>no</dd>
* <dt>in-flight cache creation</dt>
* <dd>enabled</dd>
* </dl>
*
* @param connectionFactory {@link RedisConnectionFactory} used by the {@link RedisCacheManager} to acquire
* connections to Redis when performing {@link RedisCache} operations; must not be {@literal null}.
* @return new {@link RedisCacheManager}.
* @throws IllegalArgumentException if the given {@link RedisConnectionFactory} is {@literal null}.
* @see org.springframework.data.redis.connection.RedisConnectionFactory
*/
public static RedisCacheManager create(RedisConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
return new RedisCacheManager(
org.springframework.data.redis.cache.RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory),
RedisCacheConfiguration.defaultCacheConfig());
}
/**
* Determines whether {@link RedisCache Redis caches} are allowed to be created at runtime.
*
@@ -325,11 +323,11 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* Return an {@link Collections#unmodifiableMap(Map) unmodifiable Map} containing {@link String caches name}
* mapped to the {@link RedisCache} {@link RedisCacheConfiguration configuration}.
* Return an {@link Collections#unmodifiableMap(Map) unmodifiable Map} containing {@link String caches name} mapped to
* the {@link RedisCache} {@link RedisCacheConfiguration configuration}.
*
* @return unmodifiable {@link Map} containing {@link String cache name}
* / {@link RedisCacheConfiguration configuration} pairs.
* @return unmodifiable {@link Map} containing {@link String cache name} / {@link RedisCacheConfiguration
* configuration} pairs.
*/
public Map<String, RedisCacheConfiguration> getCacheConfigurations() {
@@ -345,8 +343,8 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* Gets the default {@link RedisCacheConfiguration} applied to new {@link RedisCache} instances on creation
* when custom, non-specific {@link RedisCacheConfiguration} was not provided.
* Gets the default {@link RedisCacheConfiguration} applied to new {@link RedisCache} instances on creation when
* custom, non-specific {@link RedisCacheConfiguration} was not provided.
*
* @return the default {@link RedisCacheConfiguration}.
*/
@@ -355,8 +353,8 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* Gets a {@link Map} of {@link String cache names} to {@link RedisCacheConfiguration} objects as the initial set
* of {@link RedisCache Redis caches} to create on startup.
* Gets a {@link Map} of {@link String cache names} to {@link RedisCacheConfiguration} objects as the initial set of
* {@link RedisCache Redis caches} to create on startup.
*
* @return a {@link Map} of {@link String cache names} to {@link RedisCacheConfiguration} objects.
*/
@@ -365,8 +363,8 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* Returns a reference to the configured {@link RedisCacheWriter} used to perform {@link RedisCache} operations,
* such as reading from and writing to the cache.
* Returns a reference to the configured {@link RedisCacheWriter} used to perform {@link RedisCache} operations, such
* as reading from and writing to the cache.
*
* @return a reference to the configured {@link RedisCacheWriter}.
* @see org.springframework.data.redis.cache.RedisCacheWriter
@@ -384,8 +382,8 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
* Creates a new {@link RedisCache} with given {@link String name} and {@link RedisCacheConfiguration}.
*
* @param name {@link String name} for the {@link RedisCache}; must not be {@literal null}.
* @param cacheConfiguration {@link RedisCacheConfiguration} used to configure the {@link RedisCache};
* resolves to the {@link #getDefaultCacheConfiguration()} if {@literal null}.
* @param cacheConfiguration {@link RedisCacheConfiguration} used to configure the {@link RedisCache}; resolves to the
* {@link #getDefaultCacheConfiguration()} if {@literal null}.
* @return a new {@link RedisCache} instance; never {@literal null}.
*/
protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfiguration) {
@@ -396,8 +394,7 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
protected Collection<RedisCache> loadCaches() {
return getInitialCacheConfiguration().entrySet().stream()
.map(entry -> createRedisCache(entry.getKey(), entry.getValue()))
.toList();
.map(entry -> createRedisCache(entry.getKey(), entry.getValue())).toList();
}
private RedisCacheConfiguration resolveCacheConfiguration(@Nullable RedisCacheConfiguration cacheConfiguration) {
@@ -416,26 +413,25 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
public static class RedisCacheManagerBuilder {
/**
* Factory method returning a new {@literal Builder} used to create and configure a {@link RedisCacheManager}
* using the given {@link RedisCacheWriter}.
* Factory method returning a new {@literal Builder} used to create and configure a {@link RedisCacheManager} using
* the given {@link RedisCacheWriter}.
*
* @param cacheWriter {@link RedisCacheWriter} used to perform {@link RedisCache} operations by executing
* appropriate Redis commands; must not be {@literal null}.
* appropriate Redis commands; must not be {@literal null}.
* @return new {@link RedisCacheManagerBuilder}.
* @throws IllegalArgumentException if the given {@link RedisCacheWriter} is {@literal null}.
* @see org.springframework.data.redis.cache.RedisCacheWriter
*/
public static RedisCacheManagerBuilder fromCacheWriter(RedisCacheWriter cacheWriter) {
return new RedisCacheManagerBuilder(RedisAssertions.requireNonNull(cacheWriter,
"CacheWriter must not be null"));
return new RedisCacheManagerBuilder(RedisAssertions.requireNonNull(cacheWriter, "CacheWriter must not be null"));
}
/**
* Factory method returning a new {@literal Builder} used to create and configure a {@link RedisCacheManager}
* using the given {@link RedisConnectionFactory}.
* Factory method returning a new {@literal Builder} used to create and configure a {@link RedisCacheManager} using
* the given {@link RedisConnectionFactory}.
*
* @param connectionFactory {@link RedisConnectionFactory} used by the {@link RedisCacheManager}
* to acquire connections to Redis when performing {@link RedisCache} operations; must not be {@literal null}.
* @param connectionFactory {@link RedisConnectionFactory} used by the {@link RedisCacheManager} to acquire
* connections to Redis when performing {@link RedisCache} operations; must not be {@literal null}.
* @return new {@link RedisCacheManagerBuilder}.
* @throws IllegalArgumentException if the given {@link RedisConnectionFactory} is {@literal null}.
* @see org.springframework.data.redis.connection.RedisConnectionFactory
@@ -468,8 +464,8 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
/**
* Configure whether to allow cache creation at runtime.
*
* @param allowRuntimeCacheCreation boolean to allow creation of undeclared caches at runtime;
* {@literal true} by default.
* @param allowRuntimeCacheCreation boolean to allow creation of undeclared caches at runtime; {@literal true} by
* default.
* @return this {@link RedisCacheManagerBuilder}.
*/
public RedisCacheManagerBuilder allowCreateOnMissingCache(boolean allowRuntimeCacheCreation) {
@@ -577,8 +573,8 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
}
/**
* Registers the given {@link String cache name} and {@link RedisCacheConfiguration} used to
* create and configure a {@link RedisCache} on startup.
* Registers the given {@link String cache name} and {@link RedisCacheConfiguration} used to create and configure a
* {@link RedisCache} on startup.
*
* @param cacheName {@link String name} of the cache to register for creation on startup.
* @param cacheConfiguration {@link RedisCacheConfiguration} used to configure the new cache on startup.
@@ -644,7 +640,7 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager
public RedisCacheManager build() {
Assert.state(cacheWriter != null, "CacheWriter must not be null;"
+ " You can provide one via 'RedisCacheManagerBuilder#cacheWriter(RedisCacheWriter)'");
+ " You can provide one via 'RedisCacheManagerBuilder#cacheWriter(RedisCacheWriter)'");
RedisCacheWriter resolvedCacheWriter = !CacheStatisticsCollector.none().equals(this.statisticsCollector)
? this.cacheWriter.withStatisticsCollector(this.statisticsCollector)

View File

@@ -88,8 +88,7 @@ public interface RedisCacheWriter extends CacheStatisticsProvider {
static RedisCacheWriter lockingRedisCacheWriter(RedisConnectionFactory connectionFactory,
BatchStrategy batchStrategy) {
return lockingRedisCacheWriter(connectionFactory, Duration.ofMillis(50), TtlFunction.persistent(),
batchStrategy);
return lockingRedisCacheWriter(connectionFactory, Duration.ofMillis(50), TtlFunction.persistent(), batchStrategy);
}
/**
@@ -108,8 +107,8 @@ public interface RedisCacheWriter extends CacheStatisticsProvider {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
return new DefaultRedisCacheWriter(connectionFactory, sleepTime, lockTtlFunction,
CacheStatisticsCollector.none(), batchStrategy);
return new DefaultRedisCacheWriter(connectionFactory, sleepTime, lockTtlFunction, CacheStatisticsCollector.none(),
batchStrategy);
}
/**
@@ -124,8 +123,8 @@ public interface RedisCacheWriter extends CacheStatisticsProvider {
byte[] get(String name, byte[] key);
/**
* Get the binary value representation from Redis stored for the given key and set the given
* {@link Duration TTL expiration} for the cache entry.
* Get the binary value representation from Redis stored for the given key and set the given {@link Duration TTL
* expiration} for the cache entry.
*
* @param name must not be {@literal null}.
* @param key must not be {@literal null}.
@@ -138,18 +137,19 @@ public interface RedisCacheWriter extends CacheStatisticsProvider {
}
/**
* Determines whether the asynchronous {@link #retrieve(String, byte[])}
* and {@link #retrieve(String, byte[], Duration)} cache operations are supported by the implementation.
* Determines whether the asynchronous {@link #retrieve(String, byte[])} and
* {@link #retrieve(String, byte[], Duration)} cache operations are supported by the implementation.
* <p>
* The main factor for whether the {@literal retrieve} operation can be supported will primarily be determined by
* the Redis driver in use at runtime.
* The main factor for whether the {@literal retrieve} operation can be supported will primarily be determined by the
* Redis driver in use at runtime.
* <p>
* Returns {@literal false} by default. This will have an effect of {@link RedisCache#retrieve(Object)}
* and {@link RedisCache#retrieve(Object, Supplier)} throwing an {@link UnsupportedOperationException}.
* Returns {@literal false} by default. This will have an effect of {@link RedisCache#retrieve(Object)} and
* {@link RedisCache#retrieve(Object, Supplier)} throwing an {@link UnsupportedOperationException}.
*
* @return {@literal true} if asynchronous {@literal retrieve} operations are supported by the implementation.
* @since 3.2
*/
default boolean isRetrieveSupported() {
default boolean supportsAsyncRetrieve() {
return false;
}
@@ -162,7 +162,7 @@ public interface RedisCacheWriter extends CacheStatisticsProvider {
* @param key {@link byte[] key} mapped to the {@link CompletableFuture value} in the {@link RedisCache}.
* @return the {@link CompletableFuture value} to which the {@link RedisCache} maps the given {@link byte[] key}.
* @see #retrieve(String, byte[], Duration)
* @since 3.2.0
* @since 3.2
*/
default CompletableFuture<byte[]> retrieve(String name, byte[] key) {
return retrieve(name, key, null);
@@ -178,7 +178,7 @@ public interface RedisCacheWriter extends CacheStatisticsProvider {
* @param key {@link byte[] key} mapped to the {@link CompletableFuture value} in the {@link RedisCache}.
* @param ttl {@link Duration} specifying the {@literal expiration timeout} for the cache entry.
* @return the {@link CompletableFuture value} to which the {@link RedisCache} maps the given {@link byte[] key}.
* @since 3.2.0
* @since 3.2
*/
CompletableFuture<byte[]> retrieve(String name, byte[] key, @Nullable Duration ttl);
@@ -192,6 +192,19 @@ public interface RedisCacheWriter extends CacheStatisticsProvider {
*/
void put(String name, byte[] key, byte[] value, @Nullable Duration ttl);
/**
* Store the given key/value pair asynchronously to Redis and set the expiration time if defined.
* <p>
* This operation is non-blocking.
*
* @param name The cache name must not be {@literal null}.
* @param key The key for the cache entry. Must not be {@literal null}.
* @param value The value stored for the key. Must not be {@literal null}.
* @param ttl Optional expiration time. Can be {@literal null}.
* @since 3.2
*/
CompletableFuture<Void> store(String name, byte[] key, byte[] value, @Nullable Duration ttl);
/**
* Write the given value to Redis if the key does not already exist.
*
@@ -272,15 +285,15 @@ public interface RedisCacheWriter extends CacheStatisticsProvider {
/**
* Compute a {@link Duration time-to-live (TTL)} using the cache {@code key} and {@code value}.
* <p>
* The {@link Duration time-to-live (TTL)} is computed on each write operation. Redis uses millisecond
* granularity for timeouts. Any more granular values (e.g. micros or nanos) are not considered
* and will be truncated due to rounding. Returning {@link Duration#ZERO}, or a value less than
* {@code Duration.ofMillis(1)}, results in a persistent value that does not expire.
* The {@link Duration time-to-live (TTL)} is computed on each write operation. Redis uses millisecond granularity
* for timeouts. Any more granular values (e.g. micros or nanos) are not considered and will be truncated due to
* rounding. Returning {@link Duration#ZERO}, or a value less than {@code Duration.ofMillis(1)}, results in a
* persistent value that does not expire.
*
* @param key the cache key.
* @param value the cache value. Can be {@code null} if the cache supports {@code null} value caching.
* @return the computed {@link Duration time-to-live (TTL)}. Can be {@link Duration#ZERO} for persistent values
* (i.e. cache entry does not expire).
* (i.e. cache entry does not expire).
*/
Duration getTimeToLive(Object key, @Nullable Object value);