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
This commit is contained in:
Stéphane Nicoll
2026-09-28 10:45:01 +02:00
parent 4e4cdc3fda
commit 0fe97e3da2
4 changed files with 52 additions and 32 deletions
@@ -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<Object, Object>`.
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.
@@ -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;
}
}
@@ -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<Object, Object>> caffeine, ObjectProvider<CaffeineSpec> caffeineSpec,
ObjectProvider<CacheLoader<Object, Object>> 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<Object, Object> caffeine, CaffeineCacheManager cacheManager) {
String specification = cacheProperties.getCaffeine().getSpec();
String specification = caffeineProperties.getSpec();
if (StringUtils.hasText(specification)) {
cacheManager.setCacheSpecification(specification);
}
@@ -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));
});
}