Consistent CacheErrorHandler processing for @Cacheable(sync=true)

Closes gh-34708
This commit is contained in:
Juergen Hoeller
2025-04-04 00:22:12 +02:00
parent e7db15b325
commit 4e5979c75a
3 changed files with 298 additions and 96 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -105,7 +105,7 @@ public abstract class AbstractCacheInvoker {
return valueLoader.call();
}
catch (Exception ex2) {
throw new RuntimeException(ex2);
throw new Cache.ValueRetrievalException(key, valueLoader, ex);
}
}
}
@@ -124,16 +124,12 @@ public abstract class AbstractCacheInvoker {
try {
return cache.retrieve(key);
}
catch (Cache.ValueRetrievalException ex) {
throw ex;
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
return null;
}
}
/**
* Execute {@link Cache#retrieve(Object, Supplier)} on the specified
* {@link Cache} and invoke the error handler if an exception occurs.
@@ -146,9 +142,6 @@ public abstract class AbstractCacheInvoker {
try {
return cache.retrieve(key, valueLoader);
}
catch (Cache.ValueRetrievalException ex) {
throw ex;
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
return valueLoader.get();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
@@ -449,6 +450,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
return cacheHit;
}
@SuppressWarnings("unchecked")
@Nullable
private Object executeSynchronized(CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
@@ -456,7 +458,33 @@ 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 doRetrieve(cache, key, () -> (CompletableFuture<?>) invokeOperation(invoker));
AtomicBoolean invokeFailure = new AtomicBoolean(false);
CompletableFuture<?> result = doRetrieve(cache, key,
() -> {
CompletableFuture<?> invokeResult = ((CompletableFuture<?>) invokeOperation(invoker));
if (invokeResult == null) {
return null;
}
return invokeResult.exceptionallyCompose(ex -> {
invokeFailure.set(true);
return CompletableFuture.failedFuture(ex);
});
});
return result.exceptionallyCompose(ex -> {
if (!(ex instanceof RuntimeException rex)) {
return CompletableFuture.failedFuture(ex);
}
try {
getErrorHandler().handleCacheGetError(rex, cache, key);
if (invokeFailure.get()) {
return CompletableFuture.failedFuture(ex);
}
return (CompletableFuture) invokeOperation(invoker);
}
catch (Throwable ex2) {
return CompletableFuture.failedFuture(ex2);
}
});
}
if (this.reactiveCachingHandler != null) {
Object returnValue = this.reactiveCachingHandler.executeSynchronized(invoker, method, cache, key);
@@ -517,9 +545,17 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
if (CompletableFuture.class.isAssignableFrom(context.getMethod().getReturnType())) {
CompletableFuture<?> result = doRetrieve(cache, key);
if (result != null) {
return result.exceptionally(ex -> {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
return null;
return result.exceptionallyCompose(ex -> {
if (!(ex instanceof RuntimeException rex)) {
return CompletableFuture.failedFuture(ex);
}
try {
getErrorHandler().handleCacheGetError(rex, cache, key);
return CompletableFuture.completedFuture(null);
}
catch (Throwable ex2) {
return CompletableFuture.failedFuture(ex2);
}
}).thenCompose(value -> (CompletableFuture<?>) evaluate(
(value != null ? CompletableFuture.completedFuture(unwrapCacheValue(value)) : null),
invoker, method, contexts));
@@ -1097,32 +1133,72 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
private final ReactiveAdapterRegistry registry = ReactiveAdapterRegistry.getSharedInstance();
@SuppressWarnings({"rawtypes", "unchecked"})
@Nullable
public Object executeSynchronized(CacheOperationInvoker invoker, Method method, Cache cache, Object key) {
AtomicBoolean invokeFailure = new AtomicBoolean(false);
ReactiveAdapter adapter = this.registry.getAdapter(method.getReturnType());
if (adapter != null) {
if (adapter.isMultiValue()) {
// Flux or similar
return adapter.fromPublisher(Flux.from(Mono.fromFuture(
cache.retrieve(key,
() -> Flux.from(adapter.toPublisher(invokeOperation(invoker))).collectList().toFuture())))
.flatMap(Flux::fromIterable));
doRetrieve(cache, key,
() -> Flux.from(adapter.toPublisher(invokeOperation(invoker))).collectList().doOnError(ex -> invokeFailure.set(true)).toFuture())))
.flatMap(Flux::fromIterable)
.onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError(ex, cache, key);
if (invokeFailure.get()) {
return Flux.error(ex);
}
return Flux.from(adapter.toPublisher(invokeOperation(invoker)));
}
catch (RuntimeException exception) {
return Flux.error(exception);
}
}));
}
else {
// Mono or similar
return adapter.fromPublisher(Mono.fromFuture(
cache.retrieve(key,
() -> Mono.from(adapter.toPublisher(invokeOperation(invoker))).toFuture())));
doRetrieve(cache, key,
() -> Mono.from(adapter.toPublisher(invokeOperation(invoker))).doOnError(ex -> invokeFailure.set(true)).toFuture()))
.onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError(ex, cache, key);
if (invokeFailure.get()) {
return Mono.error(ex);
}
return Mono.from(adapter.toPublisher(invokeOperation(invoker)));
}
catch (RuntimeException exception) {
return Mono.error(exception);
}
}));
}
}
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isSuspendingFunction(method)) {
return Mono.fromFuture(cache.retrieve(key, () -> {
Mono<?> mono = ((Mono<?>) invokeOperation(invoker));
if (mono == null) {
return Mono.fromFuture(doRetrieve(cache, key, () -> {
Mono<?> mono = (Mono<?>) invokeOperation(invoker);
if (mono != null) {
mono = mono.doOnError(ex -> invokeFailure.set(true));
}
else {
mono = Mono.empty();
}
return mono.toFuture();
}));
})).onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError(ex, cache, key);
if (invokeFailure.get()) {
return Mono.error(ex);
}
return (Mono) invokeOperation(invoker);
}
catch (RuntimeException exception) {
return Mono.error(exception);
}
});
}
return NOT_HANDLED;
}
@@ -1137,7 +1213,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
return NOT_HANDLED;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"rawtypes", "unchecked"})
@Nullable
public Object findInCaches(CacheOperationContext context, Cache cache, Object key,
CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {