Skip to content

Commit bc3431f

Browse files
committed
Add resetCaches() method to Caffeine/ConcurrentMapCacheManager
Closes gh-35840
1 parent de5b9aa commit bc3431f

File tree

4 files changed

+108
-34
lines changed

4 files changed

+108
-34
lines changed

spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public class CaffeineCacheManager implements CacheManager {
7676

7777
private boolean allowNullValues = true;
7878

79-
private boolean dynamic = true;
79+
private volatile boolean dynamic = true;
8080

8181
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
8282

@@ -101,10 +101,15 @@ public CaffeineCacheManager(String... cacheNames) {
101101

102102
/**
103103
* Specify the set of cache names for this CacheManager's 'static' mode.
104-
* <p>The number of caches and their names will be fixed after a call to this method,
105-
* with no creation of further cache regions at runtime.
106-
* <p>Calling this with a {@code null} collection argument resets the
107-
* mode to 'dynamic', allowing for further creation of caches again.
104+
* <p>The number of caches and their names will be fixed after a call
105+
* to this method, with no creation of further cache regions at runtime.
106+
* <p>Note that this method replaces existing caches of the given names
107+
* and prevents the creation of further cache regions from here on - but
108+
* does <i>not</i> remove unrelated existing caches. For a full reset,
109+
* consider calling {@link #resetCaches()} before calling this method.
110+
* <p>Calling this method with a {@code null} collection argument resets
111+
* the mode to 'dynamic', allowing for further creation of caches again.
112+
* @see #resetCaches()
108113
*/
109114
public void setCacheNames(@Nullable Collection<String> cacheNames) {
110115
if (cacheNames != null) {
@@ -244,11 +249,6 @@ public boolean isAllowNullValues() {
244249
}
245250

246251

247-
@Override
248-
public Collection<String> getCacheNames() {
249-
return Collections.unmodifiableSet(this.cacheMap.keySet());
250-
}
251-
252252
@Override
253253
public @Nullable Cache getCache(String name) {
254254
Cache cache = this.cacheMap.get(name);
@@ -258,6 +258,33 @@ public Collection<String> getCacheNames() {
258258
return cache;
259259
}
260260

261+
@Override
262+
public Collection<String> getCacheNames() {
263+
return Collections.unmodifiableSet(this.cacheMap.keySet());
264+
}
265+
266+
/**
267+
* Reset this cache manager's caches, removing them completely for on-demand
268+
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
269+
* @since 6.2.14
270+
*/
271+
public void resetCaches() {
272+
this.cacheMap.values().forEach(Cache::clear);
273+
if (this.dynamic) {
274+
this.cacheMap.keySet().retainAll(this.customCacheNames);
275+
}
276+
}
277+
278+
/**
279+
* Remove the specified cache from this cache manager, applying to
280+
* custom caches as well as dynamically registered caches at runtime.
281+
* @param name the name of the cache
282+
* @since 6.1.15
283+
*/
284+
public void removeCache(String name) {
285+
this.customCacheNames.remove(name);
286+
this.cacheMap.remove(name);
287+
}
261288

262289
/**
263290
* Register the given native Caffeine Cache instance with this cache manager,
@@ -301,16 +328,6 @@ public void registerCustomCache(String name, AsyncCache<Object, Object> cache) {
301328
this.cacheMap.put(name, adaptCaffeineCache(name, cache));
302329
}
303330

304-
/**
305-
* Remove the specified cache from this cache manager, applying to
306-
* custom caches as well as dynamically registered caches at runtime.
307-
* @param name the name of the cache
308-
* @since 6.1.15
309-
*/
310-
public void removeCache(String name) {
311-
this.customCacheNames.remove(name);
312-
this.cacheMap.remove(name);
313-
}
314331

315332
/**
316333
* Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}

spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.junit.jupiter.api.Test;
2525

2626
import org.springframework.cache.Cache;
27-
import org.springframework.cache.CacheManager;
2827
import org.springframework.cache.support.SimpleValueWrapper;
2928

3029
import static org.assertj.core.api.Assertions.assertThat;
@@ -42,7 +41,7 @@ class CaffeineCacheManagerTests {
4241
@Test
4342
@SuppressWarnings("cast")
4443
void dynamicMode() {
45-
CacheManager cm = new CaffeineCacheManager();
44+
CaffeineCacheManager cm = new CaffeineCacheManager();
4645

4746
Cache cache1 = cm.getCache("c1");
4847
assertThat(cache1).isInstanceOf(CaffeineCache.class);
@@ -76,6 +75,14 @@ void dynamicMode() {
7675
cache1.evict("key3");
7776
assertThat(cache1.get("key3", () -> (String) null)).isNull();
7877
assertThat(cache1.get("key3", () -> (String) null)).isNull();
78+
79+
cm.removeCache("c1");
80+
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
81+
assertThat(cm.getCache("c2")).isSameAs(cache2);
82+
83+
cm.resetCaches();
84+
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
85+
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
7986
}
8087

8188
@Test
@@ -131,11 +138,24 @@ void staticMode() {
131138

132139
cm.setAllowNullValues(true);
133140
Cache cache1y = cm.getCache("c1");
141+
Cache cache2y = cm.getCache("c2");
134142

135143
cache1y.put("key3", null);
136144
assertThat(cache1y.get("key3").get()).isNull();
137145
cache1y.evict("key3");
138146
assertThat(cache1y.get("key3")).isNull();
147+
cache2y.put("key4", "value4");
148+
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
149+
150+
cm.removeCache("c1");
151+
assertThat(cm.getCache("c1")).isNull();
152+
assertThat(cm.getCache("c2")).isSameAs(cache2y);
153+
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
154+
155+
cm.resetCaches();
156+
assertThat(cm.getCache("c1")).isNull();
157+
assertThat(cm.getCache("c2")).isSameAs(cache2y);
158+
assertThat(cache2y.get("key4")).isNull();
139159
}
140160

141161
@Test

spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
5555

5656
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
5757

58-
private boolean dynamic = true;
58+
private volatile boolean dynamic = true;
5959

6060
private boolean allowNullValues = true;
6161

@@ -82,10 +82,15 @@ public ConcurrentMapCacheManager(String... cacheNames) {
8282

8383
/**
8484
* Specify the set of cache names for this CacheManager's 'static' mode.
85-
* <p>The number of caches and their names will be fixed after a call to this method,
86-
* with no creation of further cache regions at runtime.
87-
* <p>Calling this with a {@code null} collection argument resets the
88-
* mode to 'dynamic', allowing for further creation of caches again.
85+
* <p>The number of caches and their names will be fixed after a call
86+
* to this method, with no creation of further cache regions at runtime.
87+
* <p>Note that this method replaces existing caches of the given names
88+
* and prevents the creation of further cache regions from here on - but
89+
* does <i>not</i> remove unrelated existing caches. For a full reset,
90+
* consider calling {@link #resetCaches()} before calling this method.
91+
* <p>Calling this method with a {@code null} collection argument resets
92+
* the mode to 'dynamic', allowing for further creation of caches again.
93+
* @see #resetCaches()
8994
*/
9095
public void setCacheNames(@Nullable Collection<String> cacheNames) {
9196
if (cacheNames != null) {
@@ -160,11 +165,6 @@ public void setBeanClassLoader(ClassLoader classLoader) {
160165
}
161166

162167

163-
@Override
164-
public Collection<String> getCacheNames() {
165-
return Collections.unmodifiableSet(this.cacheMap.keySet());
166-
}
167-
168168
@Override
169169
public @Nullable Cache getCache(String name) {
170170
Cache cache = this.cacheMap.get(name);
@@ -174,6 +174,23 @@ public Collection<String> getCacheNames() {
174174
return cache;
175175
}
176176

177+
@Override
178+
public Collection<String> getCacheNames() {
179+
return Collections.unmodifiableSet(this.cacheMap.keySet());
180+
}
181+
182+
/**
183+
* Reset this cache manager's caches, removing them completely for on-demand
184+
* re-creation in 'dynamic' mode, or simply clearing their entries otherwise.
185+
* @since 6.2.14
186+
*/
187+
public void resetCaches() {
188+
this.cacheMap.values().forEach(Cache::clear);
189+
if (this.dynamic) {
190+
this.cacheMap.clear();
191+
}
192+
}
193+
177194
/**
178195
* Remove the specified cache from this cache manager.
179196
* @param name the name of the cache

spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.junit.jupiter.api.Test;
2020

2121
import org.springframework.cache.Cache;
22-
import org.springframework.cache.CacheManager;
2322

2423
import static org.assertj.core.api.Assertions.assertThat;
2524

@@ -31,7 +30,7 @@ class ConcurrentMapCacheManagerTests {
3130

3231
@Test
3332
void testDynamicMode() {
34-
CacheManager cm = new ConcurrentMapCacheManager();
33+
ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager();
3534
Cache cache1 = cm.getCache("c1");
3635
assertThat(cache1).isInstanceOf(ConcurrentMapCache.class);
3736
Cache cache1again = cm.getCache("c1");
@@ -65,6 +64,14 @@ void testDynamicMode() {
6564
assertThat(cache1.get("key3").get()).isNull();
6665
cache1.evict("key3");
6766
assertThat(cache1.get("key3")).isNull();
67+
68+
cm.removeCache("c1");
69+
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
70+
assertThat(cm.getCache("c2")).isSameAs(cache2);
71+
72+
cm.resetCaches();
73+
assertThat(cm.getCache("c1")).isNotSameAs(cache1);
74+
assertThat(cm.getCache("c2")).isNotSameAs(cache2);
6875
}
6976

7077
@Test
@@ -107,11 +114,24 @@ void testStaticMode() {
107114

108115
cm.setAllowNullValues(true);
109116
Cache cache1y = cm.getCache("c1");
117+
Cache cache2y = cm.getCache("c2");
110118

111119
cache1y.put("key3", null);
112120
assertThat(cache1y.get("key3").get()).isNull();
113121
cache1y.evict("key3");
114122
assertThat(cache1y.get("key3")).isNull();
123+
cache2y.put("key4", "value4");
124+
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
125+
126+
cm.removeCache("c1");
127+
assertThat(cm.getCache("c1")).isNull();
128+
assertThat(cm.getCache("c2")).isSameAs(cache2y);
129+
assertThat(cache2y.get("key4").get()).isEqualTo("value4");
130+
131+
cm.resetCaches();
132+
assertThat(cm.getCache("c1")).isNull();
133+
assertThat(cm.getCache("c2")).isSameAs(cache2y);
134+
assertThat(cache2y.get("key4")).isNull();
115135
}
116136

117137
@Test

0 commit comments

Comments
 (0)