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
This commit is contained in:
Stephane Nicoll 2014-08-27 16:36:01 +02:00
parent eee8184b29
commit d47c5433cb
2 changed files with 115 additions and 8 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,6 +19,7 @@ package org.springframework.cache.guava;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -29,6 +30,7 @@ import com.google.common.cache.CacheLoader;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link CacheManager} implementation that lazily builds {@link GuavaCache}
@ -45,6 +47,7 @@ import org.springframework.util.Assert;
* <p>Requires Google Guava 12.0 or higher.
*
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 4.0
* @see GuavaCache
*/
@ -81,6 +84,8 @@ public class GuavaCacheManager implements CacheManager {
* Specify the set of cache names for this CacheManager's 'static' mode.
* <p>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.
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
*/
public void setCacheNames(Collection<String> cacheNames) {
if (cacheNames != null) {
@ -89,6 +94,9 @@ public class GuavaCacheManager implements CacheManager {
}
this.dynamic = false;
}
else {
this.dynamic = true;
}
}
/**
@ -99,7 +107,7 @@ public class GuavaCacheManager implements CacheManager {
*/
public void setCacheBuilder(CacheBuilder<Object, Object> cacheBuilder) {
Assert.notNull(cacheBuilder, "CacheBuilder must not be null");
this.cacheBuilder = cacheBuilder;
doSetCacheBuilder(cacheBuilder);
}
/**
@ -109,7 +117,7 @@ public class GuavaCacheManager implements CacheManager {
* @see com.google.common.cache.CacheBuilder#from(CacheBuilderSpec)
*/
public void setCacheBuilderSpec(CacheBuilderSpec cacheBuilderSpec) {
this.cacheBuilder = CacheBuilder.from(cacheBuilderSpec);
doSetCacheBuilder(CacheBuilder.from(cacheBuilderSpec));
}
/**
@ -120,7 +128,7 @@ public class GuavaCacheManager implements CacheManager {
* @see com.google.common.cache.CacheBuilder#from(String)
*/
public void setCacheSpecification(String cacheSpecification) {
this.cacheBuilder = CacheBuilder.from(cacheSpecification);
doSetCacheBuilder(CacheBuilder.from(cacheSpecification));
}
/**
@ -131,7 +139,10 @@ public class GuavaCacheManager implements CacheManager {
* @see com.google.common.cache.LoadingCache
*/
public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
this.cacheLoader = cacheLoader;
if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) {
this.cacheLoader = cacheLoader;
refreshKnownCaches();
}
}
/**
@ -141,7 +152,10 @@ public class GuavaCacheManager implements CacheManager {
* An internal holder object will be used to store user-level {@code null}s.
*/
public void setAllowNullValues(boolean allowNullValues) {
this.allowNullValues = allowNullValues;
if (this.allowNullValues != allowNullValues) {
this.allowNullValues = allowNullValues;
refreshKnownCaches();
}
}
/**
@ -196,4 +210,20 @@ public class GuavaCacheManager implements CacheManager {
}
}
private void doSetCacheBuilder(CacheBuilder<Object, Object> cacheBuilder) {
if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) {
this.cacheBuilder = cacheBuilder;
refreshKnownCaches();
}
}
/**
* Create the known caches again with the current state of this manager.
*/
private void refreshKnownCaches() {
for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
entry.setValue(createGuavaCache(entry.getKey()));
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,15 +16,19 @@
package org.springframework.cache.guava;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import org.junit.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
/**
* @author Juergen Hoeller
* @author Stephane Nicoll
*/
public class GuavaCacheManagerTests {
@ -56,7 +60,7 @@ public class GuavaCacheManagerTests {
@Test
public void testStaticMode() {
CacheManager cm = new GuavaCacheManager("c1", "c2");
GuavaCacheManager cm = new GuavaCacheManager("c1", "c2");
Cache cache1 = cm.getCache("c1");
assertTrue(cache1 instanceof GuavaCache);
Cache cache1again = cm.getCache("c1");
@ -76,6 +80,79 @@ public class GuavaCacheManagerTests {
assertNull(cache1.get("key3").get());
cache1.evict("key3");
assertNull(cache1.get("key3"));
cm.setAllowNullValues(false);
Cache cache1x = cm.getCache("c1");
assertTrue(cache1x instanceof GuavaCache);
assertTrue(cache1x != cache1);
Cache cache2x = cm.getCache("c2");
assertTrue(cache2x instanceof GuavaCache);
assertTrue(cache2x != cache2);
Cache cache3x = cm.getCache("c3");
assertNull(cache3x);
cache1x.put("key1", "value1");
assertEquals("value1", cache1x.get("key1").get());
cache1x.put("key2", 2);
assertEquals(2, cache1x.get("key2").get());
try {
cache1x.put("key3", null);
fail("Should have thrown NullPointerException");
}
catch (NullPointerException ex) {
// expected
}
cm.setAllowNullValues(true);
Cache cache1y = cm.getCache("c1");
cache1y.put("key3", null);
assertNull(cache1y.get("key3").get());
cache1y.evict("key3");
assertNull(cache1y.get("key3"));
}
@Test
public void changeCacheSpecificationRecreateCache() {
GuavaCacheManager cm = new GuavaCacheManager("c1");
Cache cache1 = cm.getCache("c1");
CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder().maximumSize(10);
cm.setCacheBuilder(cacheBuilder);
Cache cache1x = cm.getCache("c1");
assertTrue(cache1x != cache1);
cm.setCacheBuilder(cacheBuilder); // Set same instance
Cache cache1xx = cm.getCache("c1");
assertSame(cache1x, cache1xx);
}
@Test
public void changeCacheLoaderRecreateCache() {
GuavaCacheManager cm = new GuavaCacheManager("c1");
Cache cache1 = cm.getCache("c1");
CacheLoader<Object,Object> loader = mockCacheLoader();
cm.setCacheLoader(loader);
Cache cache1x = cm.getCache("c1");
assertTrue(cache1x != cache1);
cm.setCacheLoader(loader); // Set same instance
Cache cache1xx = cm.getCache("c1");
assertSame(cache1x, cache1xx);
}
@Test
public void setCacheNameNullRestoreDynamicMode() {
GuavaCacheManager cm = new GuavaCacheManager("c1");
assertNull(cm.getCache("someCache"));
cm.setCacheNames(null);
assertNotNull(cm.getCache("someCache"));
}
@SuppressWarnings("unchecked")
private CacheLoader<Object, Object> mockCacheLoader() {
return mock(CacheLoader.class);
}
}