Handle low-level errors for sync/flux/mono/future gets

This change adds 3 protected methods to `AbstractCacheInvoker` that wrap
additional `Cache#retrieve` and `Cache#get` calls with
`handleCacheGetError` in case the Cache call itself fails.

For example, if the cache is remote and a connection to it cannot be
established.

Closes gh-21590
This commit is contained in:
Simon Baslé
2024-08-12 15:02:05 +02:00
parent 5e72ee36e2
commit 2eda5d7a2a
3 changed files with 126 additions and 5 deletions

View File

@@ -16,6 +16,10 @@
package org.springframework.cache.interceptor;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.springframework.cache.Cache;
import org.springframework.lang.Nullable;
import org.springframework.util.function.SingletonSupplier;
@@ -78,6 +82,52 @@ public abstract class AbstractCacheInvoker {
}
}
@Nullable
protected <T> T doGet(Cache cache, Object key, Callable<T> valueLoader) {
try {
return cache.get(key, valueLoader);
}
catch (Cache.ValueRetrievalException ex) {
throw ex;
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
try {
return valueLoader.call();
}
catch (Exception ex2) {
throw new RuntimeException(ex2);
}
}
}
@Nullable
protected CompletableFuture<?> doRetrieve(Cache cache, Object key) {
try {
return cache.retrieve(key);
}
catch (Cache.ValueRetrievalException ex) {
throw ex;
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
return null;
}
}
protected <T> CompletableFuture<T> doRetrieve(Cache cache, Object key, Supplier<CompletableFuture<T>> valueLoader) {
try {
return cache.retrieve(key, valueLoader);
}
catch (Cache.ValueRetrievalException ex) {
throw ex;
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
return valueLoader.get();
}
}
/**
* Execute {@link Cache#put(Object, Object)} on the specified {@link Cache}
* and invoke the error handler if an exception occurs.

View File

@@ -456,7 +456,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
if (CompletableFuture.class.isAssignableFrom(method.getReturnType())) {
return cache.retrieve(key, () -> (CompletableFuture<?>) invokeOperation(invoker));
return doRetrieve(cache, key, () -> (CompletableFuture<?>) invokeOperation(invoker));
}
if (this.reactiveCachingHandler != null) {
Object returnValue = this.reactiveCachingHandler.executeSynchronized(invoker, method, cache, key);
@@ -465,7 +465,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
}
try {
return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
return wrapCacheValue(method, doGet(cache, key, () -> unwrapReturnValue(invokeOperation(invoker))));
}
catch (Cache.ValueRetrievalException ex) {
// Directly propagate ThrowableWrapper from the invoker,
@@ -515,7 +515,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
for (Cache cache : context.getCaches()) {
if (CompletableFuture.class.isAssignableFrom(context.getMethod().getReturnType())) {
CompletableFuture<?> result = cache.retrieve(key);
CompletableFuture<?> result = doRetrieve(cache, key);
if (result != null) {
return result.exceptionally(ex -> {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
@@ -1144,7 +1144,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
ReactiveAdapter adapter = this.registry.getAdapter(context.getMethod().getReturnType());
if (adapter != null) {
CompletableFuture<?> cachedFuture = cache.retrieve(key);
CompletableFuture<?> cachedFuture = doRetrieve(cache, key);
if (cachedFuture == null) {
return null;
}