Skip to content

Commit d47c543

Browse files
committed
Improve configuration changes in GuavaCacheManager
Prior to this commit, setting the parameters used to build the caches was fragile in static mode as the caches were created right when the setCacheNames setter was called. This commit provides a better handling of such arguments and also provide a way to restore the dynamic mode if necessary. Issue: SPR-12120
1 parent eee8184 commit d47c543

File tree

2 files changed

+115
-8
lines changed

2 files changed

+115
-8
lines changed

spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCacheManager.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -19,6 +19,7 @@
1919
import java.util.Arrays;
2020
import java.util.Collection;
2121
import java.util.Collections;
22+
import java.util.Map;
2223
import java.util.concurrent.ConcurrentHashMap;
2324
import java.util.concurrent.ConcurrentMap;
2425

@@ -29,6 +30,7 @@
2930
import org.springframework.cache.Cache;
3031
import org.springframework.cache.CacheManager;
3132
import org.springframework.util.Assert;
33+
import org.springframework.util.ObjectUtils;
3234

3335
/**
3436
* {@link CacheManager} implementation that lazily builds {@link GuavaCache}
@@ -45,6 +47,7 @@
4547
* <p>Requires Google Guava 12.0 or higher.
4648
*
4749
* @author Juergen Hoeller
50+
* @author Stephane Nicoll
4851
* @since 4.0
4952
* @see GuavaCache
5053
*/
@@ -81,6 +84,8 @@ public GuavaCacheManager(String... cacheNames) {
8184
* Specify the set of cache names for this CacheManager's 'static' mode.
8285
* <p>The number of caches and their names will be fixed after a call to this method,
8386
* 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.
8489
*/
8590
public void setCacheNames(Collection<String> cacheNames) {
8691
if (cacheNames != null) {
@@ -89,6 +94,9 @@ public void setCacheNames(Collection<String> cacheNames) {
8994
}
9095
this.dynamic = false;
9196
}
97+
else {
98+
this.dynamic = true;
99+
}
92100
}
93101

94102
/**
@@ -99,7 +107,7 @@ public void setCacheNames(Collection<String> cacheNames) {
99107
*/
100108
public void setCacheBuilder(CacheBuilder<Object, Object> cacheBuilder) {
101109
Assert.notNull(cacheBuilder, "CacheBuilder must not be null");
102-
this.cacheBuilder = cacheBuilder;
110+
doSetCacheBuilder(cacheBuilder);
103111
}
104112

105113
/**
@@ -109,7 +117,7 @@ public void setCacheBuilder(CacheBuilder<Object, Object> cacheBuilder) {
109117
* @see com.google.common.cache.CacheBuilder#from(CacheBuilderSpec)
110118
*/
111119
public void setCacheBuilderSpec(CacheBuilderSpec cacheBuilderSpec) {
112-
this.cacheBuilder = CacheBuilder.from(cacheBuilderSpec);
120+
doSetCacheBuilder(CacheBuilder.from(cacheBuilderSpec));
113121
}
114122

115123
/**
@@ -120,7 +128,7 @@ public void setCacheBuilderSpec(CacheBuilderSpec cacheBuilderSpec) {
120128
* @see com.google.common.cache.CacheBuilder#from(String)
121129
*/
122130
public void setCacheSpecification(String cacheSpecification) {
123-
this.cacheBuilder = CacheBuilder.from(cacheSpecification);
131+
doSetCacheBuilder(CacheBuilder.from(cacheSpecification));
124132
}
125133

126134
/**
@@ -131,7 +139,10 @@ public void setCacheSpecification(String cacheSpecification) {
131139
* @see com.google.common.cache.LoadingCache
132140
*/
133141
public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
134-
this.cacheLoader = cacheLoader;
142+
if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) {
143+
this.cacheLoader = cacheLoader;
144+
refreshKnownCaches();
145+
}
135146
}
136147

137148
/**
@@ -141,7 +152,10 @@ public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
141152
* An internal holder object will be used to store user-level {@code null}s.
142153
*/
143154
public void setAllowNullValues(boolean allowNullValues) {
144-
this.allowNullValues = allowNullValues;
155+
if (this.allowNullValues != allowNullValues) {
156+
this.allowNullValues = allowNullValues;
157+
refreshKnownCaches();
158+
}
145159
}
146160

147161
/**
@@ -196,4 +210,20 @@ protected com.google.common.cache.Cache<Object, Object> createNativeGuavaCache(S
196210
}
197211
}
198212

213+
private void doSetCacheBuilder(CacheBuilder<Object, Object> cacheBuilder) {
214+
if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) {
215+
this.cacheBuilder = cacheBuilder;
216+
refreshKnownCaches();
217+
}
218+
}
219+
220+
/**
221+
* Create the known caches again with the current state of this manager.
222+
*/
223+
private void refreshKnownCaches() {
224+
for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
225+
entry.setValue(createGuavaCache(entry.getKey()));
226+
}
227+
}
228+
199229
}

spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheManagerTests.java

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -16,15 +16,19 @@
1616

1717
package org.springframework.cache.guava;
1818

19+
import com.google.common.cache.CacheBuilder;
20+
import com.google.common.cache.CacheLoader;
1921
import org.junit.Test;
2022

2123
import org.springframework.cache.Cache;
2224
import org.springframework.cache.CacheManager;
2325

2426
import static org.junit.Assert.*;
27+
import static org.mockito.Mockito.mock;
2528

2629
/**
2730
* @author Juergen Hoeller
31+
* @author Stephane Nicoll
2832
*/
2933
public class GuavaCacheManagerTests {
3034

@@ -56,7 +60,7 @@ public void testDynamicMode() {
5660

5761
@Test
5862
public void testStaticMode() {
59-
CacheManager cm = new GuavaCacheManager("c1", "c2");
63+
GuavaCacheManager cm = new GuavaCacheManager("c1", "c2");
6064
Cache cache1 = cm.getCache("c1");
6165
assertTrue(cache1 instanceof GuavaCache);
6266
Cache cache1again = cm.getCache("c1");
@@ -76,6 +80,79 @@ public void testStaticMode() {
7680
assertNull(cache1.get("key3").get());
7781
cache1.evict("key3");
7882
assertNull(cache1.get("key3"));
83+
84+
cm.setAllowNullValues(false);
85+
Cache cache1x = cm.getCache("c1");
86+
assertTrue(cache1x instanceof GuavaCache);
87+
assertTrue(cache1x != cache1);
88+
Cache cache2x = cm.getCache("c2");
89+
assertTrue(cache2x instanceof GuavaCache);
90+
assertTrue(cache2x != cache2);
91+
Cache cache3x = cm.getCache("c3");
92+
assertNull(cache3x);
93+
94+
cache1x.put("key1", "value1");
95+
assertEquals("value1", cache1x.get("key1").get());
96+
cache1x.put("key2", 2);
97+
assertEquals(2, cache1x.get("key2").get());
98+
try {
99+
cache1x.put("key3", null);
100+
fail("Should have thrown NullPointerException");
101+
}
102+
catch (NullPointerException ex) {
103+
// expected
104+
}
105+
106+
cm.setAllowNullValues(true);
107+
Cache cache1y = cm.getCache("c1");
108+
109+
cache1y.put("key3", null);
110+
assertNull(cache1y.get("key3").get());
111+
cache1y.evict("key3");
112+
assertNull(cache1y.get("key3"));
113+
}
114+
115+
@Test
116+
public void changeCacheSpecificationRecreateCache() {
117+
GuavaCacheManager cm = new GuavaCacheManager("c1");
118+
Cache cache1 = cm.getCache("c1");
119+
120+
CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder().maximumSize(10);
121+
cm.setCacheBuilder(cacheBuilder);
122+
Cache cache1x = cm.getCache("c1");
123+
assertTrue(cache1x != cache1);
124+
125+
cm.setCacheBuilder(cacheBuilder); // Set same instance
126+
Cache cache1xx = cm.getCache("c1");
127+
assertSame(cache1x, cache1xx);
128+
}
129+
130+
@Test
131+
public void changeCacheLoaderRecreateCache() {
132+
GuavaCacheManager cm = new GuavaCacheManager("c1");
133+
Cache cache1 = cm.getCache("c1");
134+
135+
CacheLoader<Object,Object> loader = mockCacheLoader();
136+
cm.setCacheLoader(loader);
137+
Cache cache1x = cm.getCache("c1");
138+
assertTrue(cache1x != cache1);
139+
140+
cm.setCacheLoader(loader); // Set same instance
141+
Cache cache1xx = cm.getCache("c1");
142+
assertSame(cache1x, cache1xx);
143+
}
144+
145+
@Test
146+
public void setCacheNameNullRestoreDynamicMode() {
147+
GuavaCacheManager cm = new GuavaCacheManager("c1");
148+
assertNull(cm.getCache("someCache"));
149+
cm.setCacheNames(null);
150+
assertNotNull(cm.getCache("someCache"));
151+
}
152+
153+
@SuppressWarnings("unchecked")
154+
private CacheLoader<Object, Object> mockCacheLoader() {
155+
return mock(CacheLoader.class);
79156
}
80157

81158
}

0 commit comments

Comments
 (0)