diff --git a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java index 9087df30efa..777f82cdb25 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java +++ b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java @@ -77,7 +77,7 @@ public class CaffeineCacheManager implements CacheManager { private boolean allowNullValues = true; - private boolean dynamic = true; + private volatile boolean dynamic = true; private final Map cacheMap = new ConcurrentHashMap<>(16); @@ -102,10 +102,15 @@ public class CaffeineCacheManager implements CacheManager { /** * Specify the set of cache names for this CacheManager's 'static' mode. - *

The number of caches and their names will be fixed after a call to this method, - * with no creation of further cache regions at runtime. - *

Calling this with a {@code null} collection argument resets the - * mode to 'dynamic', allowing for further creation of caches again. + *

The number of caches and their names will be fixed after a call + * to this method, with no creation of further cache regions at runtime. + *

Note that this method replaces existing caches of the given names + * and prevents the creation of further cache regions from here on - but + * does not remove unrelated existing caches. For a full reset, + * consider calling {@link #resetCaches()} before calling this method. + *

Calling this method with a {@code null} collection argument resets + * the mode to 'dynamic', allowing for further creation of caches again. + * @see #resetCaches() */ public void setCacheNames(@Nullable Collection cacheNames) { if (cacheNames != null) { @@ -245,11 +250,6 @@ public class CaffeineCacheManager implements CacheManager { } - @Override - public Collection getCacheNames() { - return Collections.unmodifiableSet(this.cacheMap.keySet()); - } - @Override @Nullable public Cache getCache(String name) { @@ -260,6 +260,33 @@ public class CaffeineCacheManager implements CacheManager { return cache; } + @Override + public Collection getCacheNames() { + return Collections.unmodifiableSet(this.cacheMap.keySet()); + } + + /** + * Reset this cache manager's caches, removing them completely for on-demand + * re-creation in 'dynamic' mode, or simply clearing their entries otherwise. + * @since 6.2.14 + */ + public void resetCaches() { + this.cacheMap.values().forEach(Cache::clear); + if (this.dynamic) { + this.cacheMap.keySet().retainAll(this.customCacheNames); + } + } + + /** + * Remove the specified cache from this cache manager, applying to + * custom caches as well as dynamically registered caches at runtime. + * @param name the name of the cache + * @since 6.1.15 + */ + public void removeCache(String name) { + this.customCacheNames.remove(name); + this.cacheMap.remove(name); + } /** * Register the given native Caffeine Cache instance with this cache manager, @@ -303,16 +330,6 @@ public class CaffeineCacheManager implements CacheManager { this.cacheMap.put(name, adaptCaffeineCache(name, cache)); } - /** - * Remove the specified cache from this cache manager, applying to - * custom caches as well as dynamically registered caches at runtime. - * @param name the name of the cache - * @since 6.1.15 - */ - public void removeCache(String name) { - this.customCacheNames.remove(name); - this.cacheMap.remove(name); - } /** * Adapt the given new native Caffeine Cache instance to Spring's {@link Cache} 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 1f4bb58c523..fdb62edef28 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 @@ -24,7 +24,6 @@ import com.github.benmanes.caffeine.cache.CaffeineSpec; import org.junit.jupiter.api.Test; import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; import org.springframework.cache.support.SimpleValueWrapper; import static org.assertj.core.api.Assertions.assertThat; @@ -42,7 +41,7 @@ class CaffeineCacheManagerTests { @Test @SuppressWarnings("cast") void dynamicMode() { - CacheManager cm = new CaffeineCacheManager(); + CaffeineCacheManager cm = new CaffeineCacheManager(); Cache cache1 = cm.getCache("c1"); assertThat(cache1).isInstanceOf(CaffeineCache.class); @@ -76,6 +75,14 @@ class CaffeineCacheManagerTests { cache1.evict("key3"); assertThat(cache1.get("key3", () -> (String) null)).isNull(); assertThat(cache1.get("key3", () -> (String) null)).isNull(); + + cm.removeCache("c1"); + assertThat(cm.getCache("c1")).isNotSameAs(cache1); + assertThat(cm.getCache("c2")).isSameAs(cache2); + + cm.resetCaches(); + assertThat(cm.getCache("c1")).isNotSameAs(cache1); + assertThat(cm.getCache("c2")).isNotSameAs(cache2); } @Test @@ -131,11 +138,24 @@ class CaffeineCacheManagerTests { cm.setAllowNullValues(true); Cache cache1y = cm.getCache("c1"); + Cache cache2y = cm.getCache("c2"); cache1y.put("key3", null); assertThat(cache1y.get("key3").get()).isNull(); cache1y.evict("key3"); assertThat(cache1y.get("key3")).isNull(); + cache2y.put("key4", "value4"); + assertThat(cache2y.get("key4").get()).isEqualTo("value4"); + + cm.removeCache("c1"); + assertThat(cm.getCache("c1")).isNull(); + assertThat(cm.getCache("c2")).isSameAs(cache2y); + assertThat(cache2y.get("key4").get()).isEqualTo("value4"); + + cm.resetCaches(); + assertThat(cm.getCache("c1")).isNull(); + assertThat(cm.getCache("c2")).isSameAs(cache2y); + assertThat(cache2y.get("key4")).isNull(); } @Test diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java index 36176e35b01..7608d524c87 100644 --- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java +++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java @@ -54,7 +54,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA private final ConcurrentMap cacheMap = new ConcurrentHashMap<>(16); - private boolean dynamic = true; + private volatile boolean dynamic = true; private boolean allowNullValues = true; @@ -82,10 +82,15 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA /** * Specify the set of cache names for this CacheManager's 'static' mode. - *

The number of caches and their names will be fixed after a call to this method, - * with no creation of further cache regions at runtime. - *

Calling this with a {@code null} collection argument resets the - * mode to 'dynamic', allowing for further creation of caches again. + *

The number of caches and their names will be fixed after a call + * to this method, with no creation of further cache regions at runtime. + *

Note that this method replaces existing caches of the given names + * and prevents the creation of further cache regions from here on - but + * does not remove unrelated existing caches. For a full reset, + * consider calling {@link #resetCaches()} before calling this method. + *

Calling this method with a {@code null} collection argument resets + * the mode to 'dynamic', allowing for further creation of caches again. + * @see #resetCaches() */ public void setCacheNames(@Nullable Collection cacheNames) { if (cacheNames != null) { @@ -160,11 +165,6 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA } - @Override - public Collection getCacheNames() { - return Collections.unmodifiableSet(this.cacheMap.keySet()); - } - @Override @Nullable public Cache getCache(String name) { @@ -175,6 +175,23 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA return cache; } + @Override + public Collection getCacheNames() { + return Collections.unmodifiableSet(this.cacheMap.keySet()); + } + + /** + * Reset this cache manager's caches, removing them completely for on-demand + * re-creation in 'dynamic' mode, or simply clearing their entries otherwise. + * @since 6.2.14 + */ + public void resetCaches() { + this.cacheMap.values().forEach(Cache::clear); + if (this.dynamic) { + this.cacheMap.clear(); + } + } + /** * Remove the specified cache from this cache manager. * @param name the name of the cache diff --git a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java index 0a366edcf1d..247dc1e3b59 100644 --- a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java +++ b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java @@ -19,7 +19,6 @@ package org.springframework.cache.concurrent; import org.junit.jupiter.api.Test; import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +30,7 @@ class ConcurrentMapCacheManagerTests { @Test void testDynamicMode() { - CacheManager cm = new ConcurrentMapCacheManager(); + ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager(); Cache cache1 = cm.getCache("c1"); assertThat(cache1).isInstanceOf(ConcurrentMapCache.class); Cache cache1again = cm.getCache("c1"); @@ -65,6 +64,14 @@ class ConcurrentMapCacheManagerTests { assertThat(cache1.get("key3").get()).isNull(); cache1.evict("key3"); assertThat(cache1.get("key3")).isNull(); + + cm.removeCache("c1"); + assertThat(cm.getCache("c1")).isNotSameAs(cache1); + assertThat(cm.getCache("c2")).isSameAs(cache2); + + cm.resetCaches(); + assertThat(cm.getCache("c1")).isNotSameAs(cache1); + assertThat(cm.getCache("c2")).isNotSameAs(cache2); } @Test @@ -107,11 +114,24 @@ class ConcurrentMapCacheManagerTests { cm.setAllowNullValues(true); Cache cache1y = cm.getCache("c1"); + Cache cache2y = cm.getCache("c2"); cache1y.put("key3", null); assertThat(cache1y.get("key3").get()).isNull(); cache1y.evict("key3"); assertThat(cache1y.get("key3")).isNull(); + cache2y.put("key4", "value4"); + assertThat(cache2y.get("key4").get()).isEqualTo("value4"); + + cm.removeCache("c1"); + assertThat(cm.getCache("c1")).isNull(); + assertThat(cm.getCache("c2")).isSameAs(cache2y); + assertThat(cache2y.get("key4").get()).isEqualTo("value4"); + + cm.resetCaches(); + assertThat(cm.getCache("c1")).isNull(); + assertThat(cm.getCache("c2")).isSameAs(cache2y); + assertThat(cache2y.get("key4")).isNull(); } @Test