From 4e4cdc3fda98fbf1127f258dd33137a9c724a44c Mon Sep 17 00:00:00 2001 From: seonghun lee Date: Sun, 20 Sep 2026 19:41:51 +0900 Subject: [PATCH] Add property to enable Caffeine async cache mode CaffeineCacheManager supports asynchronous caches backed by Caffeine's AsyncCache via setAsyncCacheMode, which adds support for Cache.retrieve. Add a spring.cache.caffeine.async property (default false) wired to CaffeineCacheManager.setAsyncCacheMode in CaffeineCacheConfiguration. See gh-51844 Signed-off-by: seonghun lee --- .../modules/reference/pages/io/caching.adoc | 2 ++ .../cache/autoconfigure/CacheProperties.java | 14 ++++++++++ .../CaffeineCacheConfiguration.java | 1 + .../CacheAutoConfigurationTests.java | 26 +++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/caching.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/caching.adoc index 62e2734f347..4eaf899dd7a 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/caching.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/caching.adoc @@ -223,6 +223,8 @@ If a javadoc:com.github.benmanes.caffeine.cache.CacheLoader[] bean is defined, i Since the javadoc:com.github.benmanes.caffeine.cache.CacheLoader[] is going to be associated with _all_ caches managed by the cache manager, it must be defined as `CacheLoader`. The auto-configuration ignores any other generic type. +To expose asynchronous caches backed by Caffeine's javadoc:com.github.benmanes.caffeine.cache.AsyncCache[], adding support for `Cache.retrieve`, set configprop:spring.cache.caffeine.async[] to `true`. + [[io.caching.provider.cache2k]] diff --git a/module/spring-boot-cache/src/main/java/org/springframework/boot/cache/autoconfigure/CacheProperties.java b/module/spring-boot-cache/src/main/java/org/springframework/boot/cache/autoconfigure/CacheProperties.java index 1affe906c4f..55561201c13 100644 --- a/module/spring-boot-cache/src/main/java/org/springframework/boot/cache/autoconfigure/CacheProperties.java +++ b/module/spring-boot-cache/src/main/java/org/springframework/boot/cache/autoconfigure/CacheProperties.java @@ -122,6 +122,12 @@ public class CacheProperties { */ private @Nullable String spec; + /** + * Whether to expose asynchronous caches backed by Caffeine's AsyncCache, adding + * support for Cache.retrieve. + */ + private boolean async; + public @Nullable String getSpec() { return this.spec; } @@ -130,6 +136,14 @@ public class CacheProperties { this.spec = spec; } + public boolean isAsync() { + return this.async; + } + + public void setAsync(boolean async) { + this.async = async; + } + } /** diff --git a/module/spring-boot-cache/src/main/java/org/springframework/boot/cache/autoconfigure/CaffeineCacheConfiguration.java b/module/spring-boot-cache/src/main/java/org/springframework/boot/cache/autoconfigure/CaffeineCacheConfiguration.java index 4a84f098cb6..dbee6303dc4 100644 --- a/module/spring-boot-cache/src/main/java/org/springframework/boot/cache/autoconfigure/CaffeineCacheConfiguration.java +++ b/module/spring-boot-cache/src/main/java/org/springframework/boot/cache/autoconfigure/CaffeineCacheConfiguration.java @@ -61,6 +61,7 @@ class CaffeineCacheConfiguration { ObjectProvider> caffeine, ObjectProvider caffeineSpec, ObjectProvider> cacheLoader) { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); + cacheManager.setAsyncCacheMode(cacheProperties.getCaffeine().isAsync()); setCacheBuilder(cacheProperties, caffeineSpec.getIfAvailable(), caffeine.getIfAvailable(), cacheManager); cacheLoader.ifAvailable(cacheManager::setCacheLoader); return cacheManager; diff --git a/module/spring-boot-cache/src/test/java/org/springframework/boot/cache/autoconfigure/CacheAutoConfigurationTests.java b/module/spring-boot-cache/src/test/java/org/springframework/boot/cache/autoconfigure/CacheAutoConfigurationTests.java index 2c3f9d1a187..fc6fef67a5a 100644 --- a/module/spring-boot-cache/src/test/java/org/springframework/boot/cache/autoconfigure/CacheAutoConfigurationTests.java +++ b/module/spring-boot-cache/src/test/java/org/springframework/boot/cache/autoconfigure/CacheAutoConfigurationTests.java @@ -80,6 +80,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; @@ -813,6 +814,31 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests { .run(this::validateCaffeineCacheWithStats); } + @Test + void caffeineCacheWithAsyncCacheMode() { + this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) + .withPropertyValues("spring.cache.type=caffeine", "spring.cache.caffeine.async=true", + "spring.cache.cacheNames=foo") + .run((context) -> { + CaffeineCacheManager manager = getCacheManager(context, CaffeineCacheManager.class); + CaffeineCache foo = (CaffeineCache) manager.getCache("foo"); + assertThat(foo).isNotNull(); + assertThat(foo.getAsyncCache()).isNotNull(); + }); + } + + @Test + void caffeineCacheWithoutAsyncCacheModeByDefault() { + this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) + .withPropertyValues("spring.cache.type=caffeine", "spring.cache.cacheNames=foo") + .run((context) -> { + CaffeineCacheManager manager = getCacheManager(context, CaffeineCacheManager.class); + CaffeineCache foo = (CaffeineCache) manager.getCache("foo"); + assertThat(foo).isNotNull(); + assertThatIllegalStateException().isThrownBy(foo::getAsyncCache); + }); + } + @Test void autoConfiguredCacheManagerCanBeSwapped() { this.contextRunner.withUserConfiguration(CacheManagerPostProcessorConfiguration.class)