Skip to content

Commit 8697026

Browse files
committed
Support for custom cache registration in CaffeineCacheManager
Closes gh-25230
1 parent 1d0fe12 commit 8697026

File tree

2 files changed

+88
-33
lines changed

2 files changed

+88
-33
lines changed

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

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import java.util.Collections;
2222
import java.util.Map;
2323
import java.util.concurrent.ConcurrentHashMap;
24-
import java.util.concurrent.ConcurrentMap;
24+
import java.util.concurrent.CopyOnWriteArrayList;
2525

2626
import com.github.benmanes.caffeine.cache.CacheLoader;
2727
import com.github.benmanes.caffeine.cache.Caffeine;
@@ -56,17 +56,19 @@
5656
*/
5757
public class CaffeineCacheManager implements CacheManager {
5858

59-
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
60-
61-
private boolean dynamic = true;
62-
6359
private Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
6460

6561
@Nullable
6662
private CacheLoader<Object, Object> cacheLoader;
6763

6864
private boolean allowNullValues = true;
6965

66+
private boolean dynamic = true;
67+
68+
private final Map<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
69+
70+
private final Collection<String> customCacheNames = new CopyOnWriteArrayList<>();
71+
7072

7173
/**
7274
* Construct a dynamic CaffeineCacheManager,
@@ -135,6 +137,13 @@ public void setCacheSpecification(String cacheSpecification) {
135137
doSetCaffeine(Caffeine.from(cacheSpecification));
136138
}
137139

140+
private void doSetCaffeine(Caffeine<Object, Object> cacheBuilder) {
141+
if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) {
142+
this.cacheBuilder = cacheBuilder;
143+
refreshCommonCaches();
144+
}
145+
}
146+
138147
/**
139148
* Set the Caffeine CacheLoader to use for building each individual
140149
* {@link CaffeineCache} instance, turning it into a LoadingCache.
@@ -145,7 +154,7 @@ public void setCacheSpecification(String cacheSpecification) {
145154
public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
146155
if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) {
147156
this.cacheLoader = cacheLoader;
148-
refreshKnownCaches();
157+
refreshCommonCaches();
149158
}
150159
}
151160

@@ -158,7 +167,7 @@ public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
158167
public void setAllowNullValues(boolean allowNullValues) {
159168
if (this.allowNullValues != allowNullValues) {
160169
this.allowNullValues = allowNullValues;
161-
refreshKnownCaches();
170+
refreshCommonCaches();
162171
}
163172
}
164173

@@ -183,42 +192,76 @@ public Cache getCache(String name) {
183192
this.dynamic ? createCaffeineCache(cacheName) : null);
184193
}
185194

195+
196+
/**
197+
* Register the given native Caffeine Cache instance with this cache manager,
198+
* adapting it to Spring's cache API for exposure through {@link #getCache}.
199+
* Any number of such custom caches may be registered side by side.
200+
* <p>This allows for custom settings per cache (as opposed to all caches
201+
* sharing the common settings in the cache manager's configuration) and
202+
* is typically used with the Caffeine builder API:
203+
* {@code registerCustomCache("myCache", Caffeine.newBuilder().maximumSize(10).build())}
204+
* <p>Note that any other caches, whether statically specified through
205+
* {@link #setCacheNames} or dynamically built on demand, still operate
206+
* with the common settings in the cache manager's configuration.
207+
* @param name the name of the cache
208+
* @param cache the custom Caffeine Cache instance to register
209+
* @since 5.2.8
210+
* @see #adaptCaffeineCache
211+
*/
212+
public void registerCustomCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache) {
213+
this.customCacheNames.add(name);
214+
this.cacheMap.put(name, adaptCaffeineCache(name, cache));
215+
}
216+
186217
/**
187-
* Create a new CaffeineCache instance for the specified cache name.
218+
* Adapt the given new native Caffeine Cache instance to Spring's {@link Cache}
219+
* abstraction for the specified cache name.
188220
* @param name the name of the cache
221+
* @param cache the native Caffeine Cache instance
189222
* @return the Spring CaffeineCache adapter (or a decorator thereof)
223+
* @since 5.2.8
224+
* @see CaffeineCache
225+
* @see #isAllowNullValues()
226+
*/
227+
protected Cache adaptCaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache) {
228+
return new CaffeineCache(name, cache, isAllowNullValues());
229+
}
230+
231+
/**
232+
* Build a common {@link CaffeineCache} instance for the specified cache name,
233+
* using the common Caffeine configuration specified on this cache manager.
234+
* <p>Delegates to {@link #adaptCaffeineCache} as the adaptation method to
235+
* Spring's cache abstraction (allowing for centralized decoration etc),
236+
* passing in a freshly built native Caffeine Cache instance.
237+
* @param name the name of the cache
238+
* @return the Spring CaffeineCache adapter (or a decorator thereof)
239+
* @see #adaptCaffeineCache
240+
* @see #createNativeCaffeineCache
190241
*/
191242
protected Cache createCaffeineCache(String name) {
192-
return new CaffeineCache(name, createNativeCaffeineCache(name), isAllowNullValues());
243+
return adaptCaffeineCache(name, createNativeCaffeineCache(name));
193244
}
194245

195246
/**
196-
* Create a native Caffeine Cache instance for the specified cache name.
247+
* Build a common Caffeine Cache instance for the specified cache name,
248+
* using the common Caffeine configuration specified on this cache manager.
197249
* @param name the name of the cache
198250
* @return the native Caffeine Cache instance
251+
* @see #createCaffeineCache
199252
*/
200253
protected com.github.benmanes.caffeine.cache.Cache<Object, Object> createNativeCaffeineCache(String name) {
201-
if (this.cacheLoader != null) {
202-
return this.cacheBuilder.build(this.cacheLoader);
203-
}
204-
else {
205-
return this.cacheBuilder.build();
206-
}
207-
}
208-
209-
private void doSetCaffeine(Caffeine<Object, Object> cacheBuilder) {
210-
if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) {
211-
this.cacheBuilder = cacheBuilder;
212-
refreshKnownCaches();
213-
}
254+
return (this.cacheLoader != null ? this.cacheBuilder.build(this.cacheLoader) : this.cacheBuilder.build());
214255
}
215256

216257
/**
217-
* Create the known caches again with the current state of this manager.
258+
* Recreate the common caches with the current state of this manager.
218259
*/
219-
private void refreshKnownCaches() {
260+
private void refreshCommonCaches() {
220261
for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
221-
entry.setValue(createCaffeineCache(entry.getKey()));
262+
if (!this.customCacheNames.contains(entry.getKey())) {
263+
entry.setValue(createCaffeineCache(entry.getKey()));
264+
}
222265
}
223266
}
224267

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -125,7 +125,7 @@ public void changeCaffeineRecreateCache() {
125125
Cache cache1x = cm.getCache("c1");
126126
assertThat(cache1x != cache1).isTrue();
127127

128-
cm.setCaffeine(caffeine); // Set same instance
128+
cm.setCaffeine(caffeine); // Set same instance
129129
Cache cache1xx = cm.getCache("c1");
130130
assertThat(cache1xx).isSameAs(cache1x);
131131
}
@@ -155,12 +155,14 @@ public void changeCacheLoaderRecreateCache() {
155155
CaffeineCacheManager cm = new CaffeineCacheManager("c1");
156156
Cache cache1 = cm.getCache("c1");
157157

158-
CacheLoader<Object, Object> loader = mockCacheLoader();
158+
@SuppressWarnings("unchecked")
159+
CacheLoader<Object, Object> loader = mock(CacheLoader.class);
160+
159161
cm.setCacheLoader(loader);
160162
Cache cache1x = cm.getCache("c1");
161163
assertThat(cache1x != cache1).isTrue();
162164

163-
cm.setCacheLoader(loader); // Set same instance
165+
cm.setCacheLoader(loader); // Set same instance
164166
Cache cache1xx = cm.getCache("c1");
165167
assertThat(cache1xx).isSameAs(cache1x);
166168
}
@@ -194,9 +196,19 @@ public Object load(Object key) throws Exception {
194196
.withMessageContaining("I only know ping");
195197
}
196198

197-
@SuppressWarnings("unchecked")
198-
private CacheLoader<Object, Object> mockCacheLoader() {
199-
return mock(CacheLoader.class);
199+
@Test
200+
public void customCacheRegistration() {
201+
CaffeineCacheManager cm = new CaffeineCacheManager("c1");
202+
com.github.benmanes.caffeine.cache.Cache<Object, Object> nc = Caffeine.newBuilder().build();
203+
cm.registerCustomCache("c2", nc);
204+
205+
Cache cache1 = cm.getCache("c1");
206+
Cache cache2 = cm.getCache("c2");
207+
assertThat(nc == cache2.getNativeCache()).isTrue();
208+
209+
cm.setCaffeine(Caffeine.newBuilder().maximumSize(10));
210+
assertThat(cm.getCache("c1") != cache1).isTrue();
211+
assertThat(cm.getCache("c2") == cache2).isTrue();
200212
}
201213

202214
}

0 commit comments

Comments
 (0)