Skip to content

Commit 78d594c

Browse files
committed
Merge branch '6.1.x'
2 parents a8e8897 + 8974da2 commit 78d594c

File tree

2 files changed

+111
-4
lines changed

2 files changed

+111
-4
lines changed

spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java

+24-3
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,10 @@ private Object findInCaches(CacheOperationContext context, Object key,
508508
if (CompletableFuture.class.isAssignableFrom(context.getMethod().getReturnType())) {
509509
CompletableFuture<?> result = cache.retrieve(key);
510510
if (result != null) {
511-
return result.thenCompose(value -> (CompletableFuture<?>) evaluate(
511+
return result.exceptionally(ex -> {
512+
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
513+
return null;
514+
}).thenCompose(value -> (CompletableFuture<?>) evaluate(
512515
(value != null ? CompletableFuture.completedFuture(unwrapCacheValue(value)) : null),
513516
invoker, method, contexts));
514517
}
@@ -1136,12 +1139,30 @@ public Object findInCaches(CacheOperationContext context, Cache cache, Object ke
11361139
if (adapter.isMultiValue()) {
11371140
return adapter.fromPublisher(Flux.from(Mono.fromFuture(cachedFuture))
11381141
.switchIfEmpty(Flux.defer(() -> (Flux) evaluate(null, invoker, method, contexts)))
1139-
.flatMap(v -> evaluate(valueToFlux(v, contexts), invoker, method, contexts)));
1142+
.flatMap(v -> evaluate(valueToFlux(v, contexts), invoker, method, contexts))
1143+
.onErrorResume(RuntimeException.class, ex -> {
1144+
try {
1145+
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
1146+
return evaluate(null, invoker, method, contexts);
1147+
}
1148+
catch (RuntimeException exception) {
1149+
return Flux.error(exception);
1150+
}
1151+
}));
11401152
}
11411153
else {
11421154
return adapter.fromPublisher(Mono.fromFuture(cachedFuture)
11431155
.switchIfEmpty(Mono.defer(() -> (Mono) evaluate(null, invoker, method, contexts)))
1144-
.flatMap(v -> evaluate(Mono.justOrEmpty(unwrapCacheValue(v)), invoker, method, contexts)));
1156+
.flatMap(v -> evaluate(Mono.justOrEmpty(unwrapCacheValue(v)), invoker, method, contexts))
1157+
.onErrorResume(RuntimeException.class, ex -> {
1158+
try {
1159+
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
1160+
return evaluate(null, invoker, method, contexts);
1161+
}
1162+
catch (RuntimeException exception) {
1163+
return Mono.error(exception);
1164+
}
1165+
}));
11451166
}
11461167
}
11471168
return NOT_HANDLED;

spring-context/src/test/java/org/springframework/cache/annotation/ReactiveCachingTests.java

+87-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import java.util.List;
2020
import java.util.concurrent.CompletableFuture;
21+
import java.util.concurrent.CompletionException;
2122
import java.util.concurrent.atomic.AtomicLong;
2223

24+
import org.junit.jupiter.api.Test;
2325
import org.junit.jupiter.params.ParameterizedTest;
2426
import org.junit.jupiter.params.provider.ValueSource;
2527
import reactor.core.publisher.Flux;
@@ -29,12 +31,15 @@
2931
import org.springframework.cache.CacheManager;
3032
import org.springframework.cache.concurrent.ConcurrentMapCache;
3133
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
34+
import org.springframework.cache.interceptor.CacheErrorHandler;
35+
import org.springframework.cache.interceptor.LoggingCacheErrorHandler;
3236
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3337
import org.springframework.context.annotation.Bean;
3438
import org.springframework.context.annotation.Configuration;
3539
import org.springframework.lang.Nullable;
3640

3741
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable;
3843

3944
/**
4045
* Tests for annotation-based caching methods that use reactive operators.
@@ -113,6 +118,51 @@ void cacheHitDetermination(Class<?> configClass) {
113118
ctx.close();
114119
}
115120

121+
@Test
122+
void cacheErrorHandlerWithLoggingCacheErrorHandler() {
123+
AnnotationConfigApplicationContext ctx =
124+
new AnnotationConfigApplicationContext(ExceptionCacheManager.class, ReactiveCacheableService.class, ErrorHandlerCachingConfiguration.class);
125+
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
126+
127+
Object key = new Object();
128+
Long r1 = service.cacheFuture(key).join();
129+
130+
assertThat(r1).isNotNull();
131+
assertThat(r1).as("cacheFuture").isEqualTo(0L);
132+
133+
key = new Object();
134+
135+
r1 = service.cacheMono(key).block();
136+
137+
assertThat(r1).isNotNull();
138+
assertThat(r1).as("cacheMono").isEqualTo(1L);
139+
140+
key = new Object();
141+
142+
r1 = service.cacheFlux(key).blockFirst();
143+
144+
assertThat(r1).isNotNull();
145+
assertThat(r1).as("cacheFlux blockFirst").isEqualTo(2L);
146+
}
147+
148+
@Test
149+
void cacheErrorHandlerWithSimpleCacheErrorHandler() {
150+
AnnotationConfigApplicationContext ctx =
151+
new AnnotationConfigApplicationContext(ExceptionCacheManager.class, ReactiveCacheableService.class);
152+
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
153+
154+
Throwable completableFuturThrowable = catchThrowable(() -> service.cacheFuture(new Object()).join());
155+
assertThat(completableFuturThrowable).isInstanceOf(CompletionException.class)
156+
.extracting(Throwable::getCause)
157+
.isInstanceOf(UnsupportedOperationException.class);
158+
159+
Throwable monoThrowable = catchThrowable(() -> service.cacheMono(new Object()).block());
160+
assertThat(monoThrowable).isInstanceOf(UnsupportedOperationException.class);
161+
162+
Throwable fluxThrowable = catchThrowable(() -> service.cacheFlux(new Object()).blockFirst());
163+
assertThat(fluxThrowable).isInstanceOf(UnsupportedOperationException.class);
164+
}
165+
116166
@ParameterizedTest
117167
@ValueSource(classes = {EarlyCacheHitDeterminationConfig.class,
118168
EarlyCacheHitDeterminationWithoutNullValuesConfig.class,
@@ -139,7 +189,6 @@ void fluxCacheDoesntDependOnFirstRequest(Class<?> configClass) {
139189
ctx.close();
140190
}
141191

142-
143192
@CacheConfig(cacheNames = "first")
144193
static class ReactiveCacheableService {
145194

@@ -242,4 +291,41 @@ public void put(Object key, @Nullable Object value) {
242291
}
243292
}
244293

294+
@Configuration
295+
static class ErrorHandlerCachingConfiguration implements CachingConfigurer {
296+
297+
@Bean
298+
@Override
299+
public CacheErrorHandler errorHandler() {
300+
return new LoggingCacheErrorHandler();
301+
}
302+
}
303+
304+
@Configuration(proxyBeanMethods = false)
305+
@EnableCaching
306+
static class ExceptionCacheManager {
307+
308+
@Bean
309+
CacheManager cacheManager() {
310+
return new ConcurrentMapCacheManager("first") {
311+
@Override
312+
protected Cache createConcurrentMapCache(String name) {
313+
return new ConcurrentMapCache(name, isAllowNullValues()) {
314+
@Override
315+
public CompletableFuture<?> retrieve(Object key) {
316+
return CompletableFuture.supplyAsync(() -> {
317+
throw new UnsupportedOperationException("Test exception on retrieve");
318+
});
319+
}
320+
321+
@Override
322+
public void put(Object key, @Nullable Object value) {
323+
throw new UnsupportedOperationException("Test exception on put");
324+
}
325+
};
326+
}
327+
};
328+
}
329+
}
330+
245331
}

0 commit comments

Comments
 (0)