Merge branch '6.2.x'
# Conflicts: # spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
This commit is contained in:
		
						commit
						907c1db7a6
					
				| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with the License.
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
| 
						 | 
					@ -104,7 +104,7 @@ public abstract class AbstractCacheInvoker {
 | 
				
			||||||
				return valueLoader.call();
 | 
									return valueLoader.call();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			catch (Exception ex2) {
 | 
								catch (Exception ex2) {
 | 
				
			||||||
				throw new RuntimeException(ex2);
 | 
									throw new Cache.ValueRetrievalException(key, valueLoader, ex);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -122,16 +122,12 @@ public abstract class AbstractCacheInvoker {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			return cache.retrieve(key);
 | 
								return cache.retrieve(key);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		catch (Cache.ValueRetrievalException ex) {
 | 
					 | 
				
			||||||
			throw ex;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		catch (RuntimeException ex) {
 | 
							catch (RuntimeException ex) {
 | 
				
			||||||
			getErrorHandler().handleCacheGetError(ex, cache, key);
 | 
								getErrorHandler().handleCacheGetError(ex, cache, key);
 | 
				
			||||||
			return null;
 | 
								return null;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Execute {@link Cache#retrieve(Object, Supplier)} on the specified
 | 
						 * Execute {@link Cache#retrieve(Object, Supplier)} on the specified
 | 
				
			||||||
	 * {@link Cache} and invoke the error handler if an exception occurs.
 | 
						 * {@link Cache} and invoke the error handler if an exception occurs.
 | 
				
			||||||
| 
						 | 
					@ -144,9 +140,6 @@ public abstract class AbstractCacheInvoker {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			return cache.retrieve(key, valueLoader);
 | 
								return cache.retrieve(key, valueLoader);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		catch (Cache.ValueRetrievalException ex) {
 | 
					 | 
				
			||||||
			throw ex;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		catch (RuntimeException ex) {
 | 
							catch (RuntimeException ex) {
 | 
				
			||||||
			getErrorHandler().handleCacheGetError(ex, cache, key);
 | 
								getErrorHandler().handleCacheGetError(ex, cache, key);
 | 
				
			||||||
			return valueLoader.get();
 | 
								return valueLoader.get();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,7 @@ import java.util.Map;
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.util.Optional;
 | 
				
			||||||
import java.util.concurrent.CompletableFuture;
 | 
					import java.util.concurrent.CompletableFuture;
 | 
				
			||||||
import java.util.concurrent.ConcurrentHashMap;
 | 
					import java.util.concurrent.ConcurrentHashMap;
 | 
				
			||||||
 | 
					import java.util.concurrent.atomic.AtomicBoolean;
 | 
				
			||||||
import java.util.function.Supplier;
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.apache.commons.logging.Log;
 | 
					import org.apache.commons.logging.Log;
 | 
				
			||||||
| 
						 | 
					@ -440,13 +441,40 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
 | 
				
			||||||
		return cacheHit;
 | 
							return cacheHit;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@SuppressWarnings("unchecked")
 | 
				
			||||||
	private @Nullable Object executeSynchronized(CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
 | 
						private @Nullable Object executeSynchronized(CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
 | 
				
			||||||
		CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
 | 
							CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
 | 
				
			||||||
		if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
 | 
							if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
 | 
				
			||||||
			Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
 | 
								Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
 | 
				
			||||||
			Cache cache = context.getCaches().iterator().next();
 | 
								Cache cache = context.getCaches().iterator().next();
 | 
				
			||||||
			if (CompletableFuture.class.isAssignableFrom(method.getReturnType())) {
 | 
								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) {
 | 
								if (this.reactiveCachingHandler != null) {
 | 
				
			||||||
				Object returnValue = this.reactiveCachingHandler.executeSynchronized(invoker, method, cache, key);
 | 
									Object returnValue = this.reactiveCachingHandler.executeSynchronized(invoker, method, cache, key);
 | 
				
			||||||
| 
						 | 
					@ -505,9 +533,17 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
 | 
				
			||||||
			if (CompletableFuture.class.isAssignableFrom(context.getMethod().getReturnType())) {
 | 
								if (CompletableFuture.class.isAssignableFrom(context.getMethod().getReturnType())) {
 | 
				
			||||||
				CompletableFuture<?> result = doRetrieve(cache, key);
 | 
									CompletableFuture<?> result = doRetrieve(cache, key);
 | 
				
			||||||
				if (result != null) {
 | 
									if (result != null) {
 | 
				
			||||||
					return result.exceptionally(ex -> {
 | 
										return result.exceptionallyCompose(ex -> {
 | 
				
			||||||
						getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
 | 
											if (!(ex instanceof RuntimeException rex)) {
 | 
				
			||||||
						return null;
 | 
												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(
 | 
										}).thenCompose(value -> (CompletableFuture<?>) evaluate(
 | 
				
			||||||
							(value != null ? CompletableFuture.completedFuture(unwrapCacheValue(value)) : null),
 | 
												(value != null ? CompletableFuture.completedFuture(unwrapCacheValue(value)) : null),
 | 
				
			||||||
							invoker, method, contexts));
 | 
												invoker, method, contexts));
 | 
				
			||||||
| 
						 | 
					@ -1075,31 +1111,71 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		private final ReactiveAdapterRegistry registry = ReactiveAdapterRegistry.getSharedInstance();
 | 
							private final ReactiveAdapterRegistry registry = ReactiveAdapterRegistry.getSharedInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@SuppressWarnings({"rawtypes", "unchecked"})
 | 
				
			||||||
		public @Nullable Object executeSynchronized(CacheOperationInvoker invoker, Method method, Cache cache, Object key) {
 | 
							public @Nullable Object executeSynchronized(CacheOperationInvoker invoker, Method method, Cache cache, Object key) {
 | 
				
			||||||
 | 
								AtomicBoolean invokeFailure = new AtomicBoolean(false);
 | 
				
			||||||
			ReactiveAdapter adapter = this.registry.getAdapter(method.getReturnType());
 | 
								ReactiveAdapter adapter = this.registry.getAdapter(method.getReturnType());
 | 
				
			||||||
			if (adapter != null) {
 | 
								if (adapter != null) {
 | 
				
			||||||
				if (adapter.isMultiValue()) {
 | 
									if (adapter.isMultiValue()) {
 | 
				
			||||||
					// Flux or similar
 | 
										// Flux or similar
 | 
				
			||||||
					return adapter.fromPublisher(Flux.from(Mono.fromFuture(
 | 
										return adapter.fromPublisher(Flux.from(Mono.fromFuture(
 | 
				
			||||||
							cache.retrieve(key,
 | 
												doRetrieve(cache, key,
 | 
				
			||||||
									() -> Flux.from(adapter.toPublisher(invokeOperation(invoker))).collectList().toFuture())))
 | 
														() -> Flux.from(adapter.toPublisher(invokeOperation(invoker))).collectList().doOnError(ex -> invokeFailure.set(true)).toFuture())))
 | 
				
			||||||
							.flatMap(Flux::fromIterable));
 | 
												.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 {
 | 
									else {
 | 
				
			||||||
					// Mono or similar
 | 
										// Mono or similar
 | 
				
			||||||
					return adapter.fromPublisher(Mono.fromFuture(
 | 
										return adapter.fromPublisher(Mono.fromFuture(
 | 
				
			||||||
							cache.retrieve(key,
 | 
												doRetrieve(cache, key,
 | 
				
			||||||
									() -> Mono.from(adapter.toPublisher(invokeOperation(invoker))).toFuture())));
 | 
														() -> 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.isSuspendingFunction(method)) {
 | 
								if (KotlinDetector.isSuspendingFunction(method)) {
 | 
				
			||||||
				return Mono.fromFuture(cache.retrieve(key, () -> {
 | 
									return Mono.fromFuture(doRetrieve(cache, key, () -> {
 | 
				
			||||||
					Mono<?> mono = ((Mono<?>) invokeOperation(invoker));
 | 
										Mono<?> mono = (Mono<?>) invokeOperation(invoker);
 | 
				
			||||||
					if (mono == null) {
 | 
										if (mono != null) {
 | 
				
			||||||
 | 
											mono = mono.doOnError(ex -> invokeFailure.set(true));
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else {
 | 
				
			||||||
						mono = Mono.empty();
 | 
											mono = Mono.empty();
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					return mono.toFuture();
 | 
										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;
 | 
								return NOT_HANDLED;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -1113,7 +1189,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
 | 
				
			||||||
			return NOT_HANDLED;
 | 
								return NOT_HANDLED;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		@SuppressWarnings({ "unchecked", "rawtypes" })
 | 
							@SuppressWarnings({"rawtypes", "unchecked"})
 | 
				
			||||||
		public @Nullable Object findInCaches(CacheOperationContext context, Cache cache, Object key,
 | 
							public @Nullable Object findInCaches(CacheOperationContext context, Cache cache, Object key,
 | 
				
			||||||
				CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
 | 
									CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with the License.
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@
 | 
				
			||||||
package org.springframework.context.aot;
 | 
					package org.springframework.context.aot;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.io.UncheckedIOException;
 | 
				
			||||||
import java.nio.file.Path;
 | 
					import java.nio.file.Path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.jspecify.annotations.Nullable;
 | 
					import org.jspecify.annotations.Nullable;
 | 
				
			||||||
| 
						 | 
					@ -103,7 +104,7 @@ public abstract class AbstractAotProcessor<T> {
 | 
				
			||||||
				FileSystemUtils.deleteRecursively(path);
 | 
									FileSystemUtils.deleteRecursively(path);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			catch (IOException ex) {
 | 
								catch (IOException ex) {
 | 
				
			||||||
				throw new RuntimeException("Failed to delete existing output in '" + path + "'");
 | 
									throw new UncheckedIOException("Failed to delete existing output in '" + path + "'", ex);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -234,7 +234,7 @@ abstract class ScheduledAnnotationReactiveSupport {
 | 
				
			||||||
					latch.await();
 | 
										latch.await();
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				catch (InterruptedException ex) {
 | 
									catch (InterruptedException ex) {
 | 
				
			||||||
					throw new RuntimeException(ex);
 | 
										Thread.currentThread().interrupt();
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			else {
 | 
								else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with the License.
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,9 @@ package org.springframework.cache.annotation;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.concurrent.CompletableFuture;
 | 
					import java.util.concurrent.CompletableFuture;
 | 
				
			||||||
import java.util.concurrent.CompletionException;
 | 
					import java.util.concurrent.CompletionException;
 | 
				
			||||||
 | 
					import java.util.concurrent.atomic.AtomicBoolean;
 | 
				
			||||||
import java.util.concurrent.atomic.AtomicLong;
 | 
					import java.util.concurrent.atomic.AtomicLong;
 | 
				
			||||||
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.jspecify.annotations.Nullable;
 | 
					import org.jspecify.annotations.Nullable;
 | 
				
			||||||
import org.junit.jupiter.api.Test;
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
| 
						 | 
					@ -40,6 +42,7 @@ import org.springframework.context.annotation.Bean;
 | 
				
			||||||
import org.springframework.context.annotation.Configuration;
 | 
					import org.springframework.context.annotation.Configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
					import static org.assertj.core.api.Assertions.assertThat;
 | 
				
			||||||
 | 
					import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 | 
				
			||||||
import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable;
 | 
					import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -58,8 +61,8 @@ class ReactiveCachingTests {
 | 
				
			||||||
			LateCacheHitDeterminationWithValueWrapperConfig.class})
 | 
								LateCacheHitDeterminationWithValueWrapperConfig.class})
 | 
				
			||||||
	void cacheHitDetermination(Class<?> configClass) {
 | 
						void cacheHitDetermination(Class<?> configClass) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		AnnotationConfigApplicationContext ctx =
 | 
							AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
 | 
				
			||||||
				new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
 | 
									configClass, ReactiveCacheableService.class);
 | 
				
			||||||
		ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
 | 
							ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Object key = new Object();
 | 
							Object key = new Object();
 | 
				
			||||||
| 
						 | 
					@ -119,68 +122,6 @@ class ReactiveCachingTests {
 | 
				
			||||||
		ctx.close();
 | 
							ctx.close();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
					 | 
				
			||||||
	void cacheErrorHandlerWithLoggingCacheErrorHandler() {
 | 
					 | 
				
			||||||
		AnnotationConfigApplicationContext ctx =
 | 
					 | 
				
			||||||
				new AnnotationConfigApplicationContext(ExceptionCacheManager.class, ReactiveCacheableService.class, ErrorHandlerCachingConfiguration.class);
 | 
					 | 
				
			||||||
		ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Object key = new Object();
 | 
					 | 
				
			||||||
		Long r1 = service.cacheFuture(key).join();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		assertThat(r1).isNotNull();
 | 
					 | 
				
			||||||
		assertThat(r1).as("cacheFuture").isEqualTo(0L);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		key = new Object();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		r1 = service.cacheMono(key).block();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		assertThat(r1).isNotNull();
 | 
					 | 
				
			||||||
		assertThat(r1).as("cacheMono").isEqualTo(1L);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		key = new Object();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		r1 = service.cacheFlux(key).blockFirst();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		assertThat(r1).isNotNull();
 | 
					 | 
				
			||||||
		assertThat(r1).as("cacheFlux blockFirst").isEqualTo(2L);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Test
 | 
					 | 
				
			||||||
	void cacheErrorHandlerWithLoggingCacheErrorHandlerAndMethodError() {
 | 
					 | 
				
			||||||
		AnnotationConfigApplicationContext ctx =
 | 
					 | 
				
			||||||
				new AnnotationConfigApplicationContext(ExceptionCacheManager.class, ReactiveFailureCacheableService.class, ErrorHandlerCachingConfiguration.class);
 | 
					 | 
				
			||||||
		ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Object key = new Object();
 | 
					 | 
				
			||||||
		StepVerifier.create(service.cacheMono(key))
 | 
					 | 
				
			||||||
				.expectErrorMessage("mono service error")
 | 
					 | 
				
			||||||
				.verify();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		key = new Object();
 | 
					 | 
				
			||||||
		StepVerifier.create(service.cacheFlux(key))
 | 
					 | 
				
			||||||
				.expectErrorMessage("flux service error")
 | 
					 | 
				
			||||||
				.verify();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Test
 | 
					 | 
				
			||||||
	void cacheErrorHandlerWithSimpleCacheErrorHandler() {
 | 
					 | 
				
			||||||
		AnnotationConfigApplicationContext ctx =
 | 
					 | 
				
			||||||
				new AnnotationConfigApplicationContext(ExceptionCacheManager.class, ReactiveCacheableService.class);
 | 
					 | 
				
			||||||
		ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Throwable completableFuturThrowable = catchThrowable(() -> service.cacheFuture(new Object()).join());
 | 
					 | 
				
			||||||
		assertThat(completableFuturThrowable).isInstanceOf(CompletionException.class)
 | 
					 | 
				
			||||||
				.extracting(Throwable::getCause)
 | 
					 | 
				
			||||||
				.isInstanceOf(UnsupportedOperationException.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Throwable monoThrowable = catchThrowable(() -> service.cacheMono(new Object()).block());
 | 
					 | 
				
			||||||
		assertThat(monoThrowable).isInstanceOf(UnsupportedOperationException.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Throwable fluxThrowable = catchThrowable(() -> service.cacheFlux(new Object()).blockFirst());
 | 
					 | 
				
			||||||
		assertThat(fluxThrowable).isInstanceOf(UnsupportedOperationException.class);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@ParameterizedTest
 | 
						@ParameterizedTest
 | 
				
			||||||
	@ValueSource(classes = {EarlyCacheHitDeterminationConfig.class,
 | 
						@ValueSource(classes = {EarlyCacheHitDeterminationConfig.class,
 | 
				
			||||||
			EarlyCacheHitDeterminationWithoutNullValuesConfig.class,
 | 
								EarlyCacheHitDeterminationWithoutNullValuesConfig.class,
 | 
				
			||||||
| 
						 | 
					@ -188,8 +129,8 @@ class ReactiveCachingTests {
 | 
				
			||||||
			LateCacheHitDeterminationWithValueWrapperConfig.class})
 | 
								LateCacheHitDeterminationWithValueWrapperConfig.class})
 | 
				
			||||||
	void fluxCacheDoesntDependOnFirstRequest(Class<?> configClass) {
 | 
						void fluxCacheDoesntDependOnFirstRequest(Class<?> configClass) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		AnnotationConfigApplicationContext ctx =
 | 
							AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
 | 
				
			||||||
				new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
 | 
									configClass, ReactiveCacheableService.class);
 | 
				
			||||||
		ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
 | 
							ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Object key = new Object();
 | 
							Object key = new Object();
 | 
				
			||||||
| 
						 | 
					@ -207,6 +148,117 @@ class ReactiveCachingTests {
 | 
				
			||||||
		ctx.close();
 | 
							ctx.close();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void cacheErrorHandlerWithSimpleCacheErrorHandler() {
 | 
				
			||||||
 | 
							AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
 | 
				
			||||||
 | 
									ExceptionCacheManager.class, ReactiveCacheableService.class);
 | 
				
			||||||
 | 
							ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Throwable completableFutureThrowable = catchThrowable(() -> service.cacheFuture(new Object()).join());
 | 
				
			||||||
 | 
							assertThat(completableFutureThrowable).isInstanceOf(CompletionException.class)
 | 
				
			||||||
 | 
									.extracting(Throwable::getCause)
 | 
				
			||||||
 | 
									.isInstanceOf(UnsupportedOperationException.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Throwable monoThrowable = catchThrowable(() -> service.cacheMono(new Object()).block());
 | 
				
			||||||
 | 
							assertThat(monoThrowable).isInstanceOf(UnsupportedOperationException.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Throwable fluxThrowable = catchThrowable(() -> service.cacheFlux(new Object()).blockFirst());
 | 
				
			||||||
 | 
							assertThat(fluxThrowable).isInstanceOf(UnsupportedOperationException.class);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void cacheErrorHandlerWithSimpleCacheErrorHandlerAndSync() {
 | 
				
			||||||
 | 
							AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
 | 
				
			||||||
 | 
									ExceptionCacheManager.class, ReactiveSyncCacheableService.class);
 | 
				
			||||||
 | 
							ReactiveSyncCacheableService service = ctx.getBean(ReactiveSyncCacheableService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Throwable completableFutureThrowable = catchThrowable(() -> service.cacheFuture(new Object()).join());
 | 
				
			||||||
 | 
							assertThat(completableFutureThrowable).isInstanceOf(CompletionException.class)
 | 
				
			||||||
 | 
									.extracting(Throwable::getCause)
 | 
				
			||||||
 | 
									.isInstanceOf(UnsupportedOperationException.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Throwable monoThrowable = catchThrowable(() -> service.cacheMono(new Object()).block());
 | 
				
			||||||
 | 
							assertThat(monoThrowable).isInstanceOf(UnsupportedOperationException.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Throwable fluxThrowable = catchThrowable(() -> service.cacheFlux(new Object()).blockFirst());
 | 
				
			||||||
 | 
							assertThat(fluxThrowable).isInstanceOf(UnsupportedOperationException.class);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void cacheErrorHandlerWithLoggingCacheErrorHandler() {
 | 
				
			||||||
 | 
							AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
 | 
				
			||||||
 | 
									ExceptionCacheManager.class, ReactiveCacheableService.class, ErrorHandlerCachingConfiguration.class);
 | 
				
			||||||
 | 
							ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Long r1 = service.cacheFuture(new Object()).join();
 | 
				
			||||||
 | 
							assertThat(r1).isNotNull();
 | 
				
			||||||
 | 
							assertThat(r1).as("cacheFuture").isEqualTo(0L);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r1 = service.cacheMono(new Object()).block();
 | 
				
			||||||
 | 
							assertThat(r1).isNotNull();
 | 
				
			||||||
 | 
							assertThat(r1).as("cacheMono").isEqualTo(1L);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r1 = service.cacheFlux(new Object()).blockFirst();
 | 
				
			||||||
 | 
							assertThat(r1).isNotNull();
 | 
				
			||||||
 | 
							assertThat(r1).as("cacheFlux blockFirst").isEqualTo(2L);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void cacheErrorHandlerWithLoggingCacheErrorHandlerAndSync() {
 | 
				
			||||||
 | 
							AnnotationConfigApplicationContext ctx =
 | 
				
			||||||
 | 
									new AnnotationConfigApplicationContext(ExceptionCacheManager.class, ReactiveSyncCacheableService.class, ErrorHandlerCachingConfiguration.class);
 | 
				
			||||||
 | 
							ReactiveSyncCacheableService service = ctx.getBean(ReactiveSyncCacheableService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Long r1 = service.cacheFuture(new Object()).join();
 | 
				
			||||||
 | 
							assertThat(r1).isNotNull();
 | 
				
			||||||
 | 
							assertThat(r1).as("cacheFuture").isEqualTo(0L);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r1 = service.cacheMono(new Object()).block();
 | 
				
			||||||
 | 
							assertThat(r1).isNotNull();
 | 
				
			||||||
 | 
							assertThat(r1).as("cacheMono").isEqualTo(1L);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r1 = service.cacheFlux(new Object()).blockFirst();
 | 
				
			||||||
 | 
							assertThat(r1).isNotNull();
 | 
				
			||||||
 | 
							assertThat(r1).as("cacheFlux blockFirst").isEqualTo(2L);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void cacheErrorHandlerWithLoggingCacheErrorHandlerAndOperationException() {
 | 
				
			||||||
 | 
							AnnotationConfigApplicationContext ctx =
 | 
				
			||||||
 | 
									new AnnotationConfigApplicationContext(EarlyCacheHitDeterminationConfig.class, ReactiveFailureCacheableService.class, ErrorHandlerCachingConfiguration.class);
 | 
				
			||||||
 | 
							ReactiveFailureCacheableService service = ctx.getBean(ReactiveFailureCacheableService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assertThatExceptionOfType(CompletionException.class).isThrownBy(() -> service.cacheFuture(new Object()).join())
 | 
				
			||||||
 | 
									.withMessage(IllegalStateException.class.getName() + ": future service error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							StepVerifier.create(service.cacheMono(new Object()))
 | 
				
			||||||
 | 
									.expectErrorMessage("mono service error")
 | 
				
			||||||
 | 
									.verify();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							StepVerifier.create(service.cacheFlux(new Object()))
 | 
				
			||||||
 | 
									.expectErrorMessage("flux service error")
 | 
				
			||||||
 | 
									.verify();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void cacheErrorHandlerWithLoggingCacheErrorHandlerAndOperationExceptionAndSync() {
 | 
				
			||||||
 | 
							AnnotationConfigApplicationContext ctx =
 | 
				
			||||||
 | 
									new AnnotationConfigApplicationContext(EarlyCacheHitDeterminationConfig.class, ReactiveSyncFailureCacheableService.class, ErrorHandlerCachingConfiguration.class);
 | 
				
			||||||
 | 
							ReactiveSyncFailureCacheableService service = ctx.getBean(ReactiveSyncFailureCacheableService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assertThatExceptionOfType(CompletionException.class).isThrownBy(() -> service.cacheFuture(new Object()).join())
 | 
				
			||||||
 | 
									.withMessage(IllegalStateException.class.getName() + ": future service error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							StepVerifier.create(service.cacheMono(new Object()))
 | 
				
			||||||
 | 
									.expectErrorMessage("mono service error")
 | 
				
			||||||
 | 
									.verify();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							StepVerifier.create(service.cacheFlux(new Object()))
 | 
				
			||||||
 | 
									.expectErrorMessage("flux service error")
 | 
				
			||||||
 | 
									.verify();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@CacheConfig(cacheNames = "first")
 | 
						@CacheConfig(cacheNames = "first")
 | 
				
			||||||
	static class ReactiveCacheableService {
 | 
						static class ReactiveCacheableService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -232,16 +284,94 @@ class ReactiveCachingTests {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@CacheConfig(cacheNames = "first")
 | 
						@CacheConfig(cacheNames = "first")
 | 
				
			||||||
	static class ReactiveFailureCacheableService extends ReactiveCacheableService {
 | 
						static class ReactiveSyncCacheableService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final AtomicLong counter = new AtomicLong();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Cacheable(sync = true)
 | 
				
			||||||
 | 
							CompletableFuture<Long> cacheFuture(Object arg) {
 | 
				
			||||||
 | 
								return CompletableFuture.completedFuture(this.counter.getAndIncrement());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Cacheable(sync = true)
 | 
				
			||||||
 | 
							Mono<Long> cacheMono(Object arg) {
 | 
				
			||||||
 | 
								return Mono.defer(() -> Mono.just(this.counter.getAndIncrement()));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Cacheable(sync = true)
 | 
				
			||||||
 | 
							Flux<Long> cacheFlux(Object arg) {
 | 
				
			||||||
 | 
								return Flux.defer(() -> Flux.just(this.counter.getAndIncrement(), 0L, -1L, -2L, -3L));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@CacheConfig(cacheNames = "first")
 | 
				
			||||||
 | 
						static class ReactiveFailureCacheableService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final AtomicBoolean cacheFutureInvoked = new AtomicBoolean();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final AtomicBoolean cacheMonoInvoked = new AtomicBoolean();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final AtomicBoolean cacheFluxInvoked = new AtomicBoolean();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Cacheable
 | 
				
			||||||
 | 
							CompletableFuture<Long> cacheFuture(Object arg) {
 | 
				
			||||||
 | 
								if (!this.cacheFutureInvoked.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
									return CompletableFuture.failedFuture(new IllegalStateException("future service invoked twice"));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return CompletableFuture.failedFuture(new IllegalStateException("future service error"));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		@Cacheable
 | 
							@Cacheable
 | 
				
			||||||
		Mono<Long> cacheMono(Object arg) {
 | 
							Mono<Long> cacheMono(Object arg) {
 | 
				
			||||||
 | 
								if (!this.cacheMonoInvoked.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
									return Mono.error(new IllegalStateException("mono service invoked twice"));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			return Mono.error(new IllegalStateException("mono service error"));
 | 
								return Mono.error(new IllegalStateException("mono service error"));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		@Cacheable
 | 
							@Cacheable
 | 
				
			||||||
		Flux<Long> cacheFlux(Object arg) {
 | 
							Flux<Long> cacheFlux(Object arg) {
 | 
				
			||||||
 | 
								if (!this.cacheFluxInvoked.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
									return Flux.error(new IllegalStateException("flux service invoked twice"));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return Flux.error(new IllegalStateException("flux service error"));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@CacheConfig(cacheNames = "first")
 | 
				
			||||||
 | 
						static class ReactiveSyncFailureCacheableService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final AtomicBoolean cacheFutureInvoked = new AtomicBoolean();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final AtomicBoolean cacheMonoInvoked = new AtomicBoolean();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final AtomicBoolean cacheFluxInvoked = new AtomicBoolean();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Cacheable(sync = true)
 | 
				
			||||||
 | 
							CompletableFuture<Long> cacheFuture(Object arg) {
 | 
				
			||||||
 | 
								if (!this.cacheFutureInvoked.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
									return CompletableFuture.failedFuture(new IllegalStateException("future service invoked twice"));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return CompletableFuture.failedFuture(new IllegalStateException("future service error"));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Cacheable(sync = true)
 | 
				
			||||||
 | 
							Mono<Long> cacheMono(Object arg) {
 | 
				
			||||||
 | 
								if (!this.cacheMonoInvoked.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
									return Mono.error(new IllegalStateException("mono service invoked twice"));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return Mono.error(new IllegalStateException("mono service error"));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Cacheable(sync = true)
 | 
				
			||||||
 | 
							Flux<Long> cacheFlux(Object arg) {
 | 
				
			||||||
 | 
								if (!this.cacheFluxInvoked.compareAndSet(false, true)) {
 | 
				
			||||||
 | 
									return Flux.error(new IllegalStateException("flux service invoked twice"));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			return Flux.error(new IllegalStateException("flux service error"));
 | 
								return Flux.error(new IllegalStateException("flux service error"));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -323,6 +453,7 @@ class ReactiveCachingTests {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Configuration
 | 
						@Configuration
 | 
				
			||||||
	static class ErrorHandlerCachingConfiguration implements CachingConfigurer {
 | 
						static class ErrorHandlerCachingConfiguration implements CachingConfigurer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -333,6 +464,7 @@ class ReactiveCachingTests {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Configuration(proxyBeanMethods = false)
 | 
						@Configuration(proxyBeanMethods = false)
 | 
				
			||||||
	@EnableCaching
 | 
						@EnableCaching
 | 
				
			||||||
	static class ExceptionCacheManager {
 | 
						static class ExceptionCacheManager {
 | 
				
			||||||
| 
						 | 
					@ -345,11 +477,12 @@ class ReactiveCachingTests {
 | 
				
			||||||
					return new ConcurrentMapCache(name, isAllowNullValues()) {
 | 
										return new ConcurrentMapCache(name, isAllowNullValues()) {
 | 
				
			||||||
						@Override
 | 
											@Override
 | 
				
			||||||
						public CompletableFuture<?> retrieve(Object key) {
 | 
											public CompletableFuture<?> retrieve(Object key) {
 | 
				
			||||||
							return CompletableFuture.supplyAsync(() -> {
 | 
												return CompletableFuture.failedFuture(new UnsupportedOperationException("Test exception on retrieve"));
 | 
				
			||||||
								throw new UnsupportedOperationException("Test exception on retrieve");
 | 
											}
 | 
				
			||||||
							});
 | 
											@Override
 | 
				
			||||||
 | 
											public <T> CompletableFuture<T> retrieve(Object key, Supplier<CompletableFuture<T>> valueLoader) {
 | 
				
			||||||
 | 
												return CompletableFuture.failedFuture(new UnsupportedOperationException("Test exception on retrieve"));
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					 | 
				
			||||||
						@Override
 | 
											@Override
 | 
				
			||||||
						public void put(Object key, @Nullable Object value) {
 | 
											public void put(Object key, @Nullable Object value) {
 | 
				
			||||||
							throw new UnsupportedOperationException("Test exception on put");
 | 
												throw new UnsupportedOperationException("Test exception on put");
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with the License.
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
| 
						 | 
					@ -189,7 +189,7 @@ public abstract class AbstractMockMvcBuilder<B extends AbstractMockMvcBuilder<B>
 | 
				
			||||||
					filterDecorator.initIfRequired(servletContext);
 | 
										filterDecorator.initIfRequired(servletContext);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				catch (ServletException ex) {
 | 
									catch (ServletException ex) {
 | 
				
			||||||
					throw new RuntimeException("Failed to initialize Filter " + filter, ex);
 | 
										throw new IllegalStateException("Failed to initialize Filter " + filter, ex);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with the License.
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,6 @@ import java.util.Locale;
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public interface SmartView extends View {
 | 
					public interface SmartView extends View {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Whether the view performs a redirect.
 | 
						 * Whether the view performs a redirect.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with the License.
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
| 
						 | 
					@ -404,7 +404,7 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur
 | 
				
			||||||
				throw ex;
 | 
									throw ex;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			catch (Exception ex) {
 | 
								catch (Exception ex) {
 | 
				
			||||||
				throw new RuntimeException("Failed to render " + modelAndView, ex);
 | 
									throw new IllegalStateException("Failed to render " + modelAndView, ex);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			finally {
 | 
								finally {
 | 
				
			||||||
				RequestContextHolder.resetRequestAttributes();
 | 
									RequestContextHolder.resetRequestAttributes();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue