From 824bc09d1135f43351c7accc0e816e0e0e1a06bc Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 22 Nov 2023 15:56:26 +0100 Subject: [PATCH] Actual caching of null values in retrieve(key, valueLoader) See gh-31637 --- .../springframework/cache/caffeine/CaffeineCache.java | 9 ++++++++- .../cache/caffeine/CaffeineCacheManagerTests.java | 2 ++ .../src/main/java/org/springframework/cache/Cache.java | 8 ++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java index c3999adbe0a5..da4b62e471c6 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java +++ b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java @@ -148,7 +148,14 @@ public CompletableFuture retrieve(Object key) { @SuppressWarnings("unchecked") @Override public CompletableFuture retrieve(Object key, Supplier> valueLoader) { - return (CompletableFuture) getAsyncCache().get(key, (k, e) -> valueLoader.get()); + if (isAllowNullValues()) { + return (CompletableFuture) getAsyncCache() + .get(key, (k, e) -> valueLoader.get().thenApply(this::toStoreValue)) + .thenApply(this::fromStoreValue); + } + else { + return (CompletableFuture) getAsyncCache().get(key, (k, e) -> valueLoader.get()); + } } @Override diff --git a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java index b8d5a4006b6b..4e81fdb33cfc 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java @@ -181,7 +181,9 @@ void asyncMode() { assertThat(cache1.retrieve("key3", () -> CompletableFuture.completedFuture("value3")).join()) .isEqualTo("value3"); cache1.evict("key3"); + assertThat(cache1.retrieve("key3")).isNull(); assertThat(cache1.retrieve("key3", () -> CompletableFuture.completedFuture(null)).join()).isNull(); + assertThat(cache1.retrieve("key3").join()).isEqualTo(new SimpleValueWrapper(null)); assertThat(cache1.retrieve("key3", () -> CompletableFuture.completedFuture(null)).join()).isNull(); } diff --git a/spring-context/src/main/java/org/springframework/cache/Cache.java b/spring-context/src/main/java/org/springframework/cache/Cache.java index 862192de3c13..0d8b352ff264 100644 --- a/spring-context/src/main/java/org/springframework/cache/Cache.java +++ b/spring-context/src/main/java/org/springframework/cache/Cache.java @@ -151,10 +151,10 @@ default CompletableFuture retrieve(Object key) { *

If possible, implementations should ensure that the loading operation * is synchronized so that the specified {@code valueLoader} is only called * once in case of concurrent access on the same key. - *

Null values are generally not supported by this method. The provided - * {@link CompletableFuture} handle produces a value or raises an exception. - * If the {@code valueLoader} raises an exception, it will be propagated - * to the {@code CompletableFuture} handle returned from here. + *

Null values always indicate a user-level {@code null} value with this + * method. The provided {@link CompletableFuture} handle produces a value + * or raises an exception. If the {@code valueLoader} raises an exception, + * it will be propagated to the returned {@code CompletableFuture} handle. * @param key the key whose associated value is to be returned * @return the value to which this cache maps the specified key, contained * within a {@link CompletableFuture} which will never be {@code null}.