From 0fe97e3da2fdecb9e644bb1fbdfa1157dc030a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Mon, 28 Sep 2026 10:39:48 +0200 Subject: [PATCH] Polish "Add property to enable Caffeine async cache mode" Restructured to spring.cache.caffeine.cache-mode with an enum that can be "native" or "async". Also clarified in the documentation the scope of the property and that custom caches are not affected. See gh-51844 --- .../modules/reference/pages/io/caching.adoc | 5 ++- .../cache/autoconfigure/CacheProperties.java | 34 +++++++++++++------ .../CaffeineCacheConfiguration.java | 12 ++++--- .../CacheAutoConfigurationTests.java | 33 +++++++++--------- 4 files changed, 52 insertions(+), 32 deletions(-) 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 4eaf899dd7a..5e273a4525e 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,7 +223,10 @@ 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`. +To enable the use of `Cache#retrieve`, an javadoc:com.github.benmanes.caffeine.cache.AsyncCache[] should be available. +Such caches can be created explicitly with a `CacheManagerCustomizer` bean that calls `CaffeineCacheManager#registerCustomCache`. +Alternatively, named caches and caches on demand can be async by setting configprop:spring.cache.caffeine.cache-mode[] to `async`. +Regular, native, caches can still be configured explicitly using a customizer bean. 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 55561201c13..a76020f9b88 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 @@ -116,17 +116,24 @@ public class CacheProperties { */ public static class Caffeine { + /** + * Mode to use for configured cache names or caches created on demand. + */ + private CacheMode cacheMode = CacheMode.NATIVE; + /** * The spec to use to create caches. See CaffeineSpec for more details on the spec * format. */ private @Nullable String spec; - /** - * Whether to expose asynchronous caches backed by Caffeine's AsyncCache, adding - * support for Cache.retrieve. - */ - private boolean async; + public CacheMode getCacheMode() { + return this.cacheMode; + } + + public void setCacheMode(CacheMode cacheMode) { + this.cacheMode = cacheMode; + } public @Nullable String getSpec() { return this.spec; @@ -136,12 +143,19 @@ public class CacheProperties { this.spec = spec; } - public boolean isAsync() { - return this.async; - } + public enum CacheMode { + + /** + * Use regular caches backed by Caffeine's Cache. + */ + NATIVE, + + /** + * Use asynchronous caches backed by Caffeine's AsyncCache, adding support for + * Cache.retrieve. + */ + 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 dbee6303dc4..3122c7ad70c 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 @@ -26,6 +26,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.cache.autoconfigure.CacheProperties.Caffeine.CacheMode; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; @@ -61,15 +62,18 @@ class CaffeineCacheConfiguration { ObjectProvider> caffeine, ObjectProvider caffeineSpec, ObjectProvider> cacheLoader) { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); - cacheManager.setAsyncCacheMode(cacheProperties.getCaffeine().isAsync()); - setCacheBuilder(cacheProperties, caffeineSpec.getIfAvailable(), caffeine.getIfAvailable(), cacheManager); + CacheProperties.Caffeine caffeineProperties = cacheProperties.getCaffeine(); + if (caffeineProperties.getCacheMode() == CacheMode.ASYNC) { + cacheManager.setAsyncCacheMode(true); + } + setCacheBuilder(caffeineProperties, caffeineSpec.getIfAvailable(), caffeine.getIfAvailable(), cacheManager); cacheLoader.ifAvailable(cacheManager::setCacheLoader); return cacheManager; } - private void setCacheBuilder(CacheProperties cacheProperties, @Nullable CaffeineSpec caffeineSpec, + private void setCacheBuilder(CacheProperties.Caffeine caffeineProperties, @Nullable CaffeineSpec caffeineSpec, @Nullable Caffeine caffeine, CaffeineCacheManager cacheManager) { - String specification = cacheProperties.getCaffeine().getSpec(); + String specification = caffeineProperties.getSpec(); if (StringUtils.hasText(specification)) { cacheManager.setCacheSpecification(specification); } 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 fc6fef67a5a..7ff66a1c622 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 @@ -81,6 +81,7 @@ 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.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; @@ -815,27 +816,25 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests { } @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() { + void caffeineCacheUsesNativeModeByDefault() { 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); + assertThat(manager.getCache("foo")).isInstanceOfSatisfying(CaffeineCache.class, + (cache) -> assertThatIllegalStateException().isThrownBy(cache::getAsyncCache)); + }); + } + + @Test + void caffeineCacheWithAsyncCacheMode() { + this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) + .withPropertyValues("spring.cache.type=caffeine", "spring.cache.caffeine.cache-mode=async", + "spring.cache.cacheNames=foo") + .run((context) -> { + CaffeineCacheManager manager = getCacheManager(context, CaffeineCacheManager.class); + assertThat(manager.getCache("foo")).isInstanceOfSatisfying(CaffeineCache.class, + (cache) -> assertThatNoException().isThrownBy(cache::getAsyncCache)); }); }