diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java
index 6865dd217e..26e4059420 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheFactoryBean.java
@@ -52,9 +52,10 @@ import org.springframework.beans.factory.InitializingBean;
* Cache instance will be retrieved from the CacheManager.
*
*
Note: As of Spring 4.0, Spring's EhCache support requires EhCache 2.1 or higher.
+ * We recommend the use of EhCache 2.5 or higher.
- * @author Dmitriy Kopylenko
* @author Juergen Hoeller
+ * @author Dmitriy Kopylenko
* @since 1.1.1
* @see #setCacheManager
* @see EhCacheManagerFactoryBean
@@ -87,12 +88,14 @@ public class EhCacheFactoryBean extends CacheConfiguration implements FactoryBea
@SuppressWarnings("deprecation")
public EhCacheFactoryBean() {
+ // Using deprecated setMaxElementsInMemory method for EhCache 2.1-2.4 compatibility
setMaxElementsInMemory(10000);
setMaxElementsOnDisk(10000000);
setTimeToLiveSeconds(120);
setTimeToIdleSeconds(120);
}
+
/**
* Set a CacheManager from which to retrieve a named Cache instance.
* By default, {@code CacheManager.getInstance()} will be called.
diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java
index f6663b48a8..a17adb1426 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java
@@ -45,9 +45,10 @@ import org.springframework.core.io.Resource;
* also necessary for loading EhCache configuration from a non-default config location.
*
*
Note: As of Spring 4.0, Spring's EhCache support requires EhCache 2.1 or higher.
+ * We recommend the use of EhCache 2.5 or higher.
*
- * @author Dmitriy Kopylenko
* @author Juergen Hoeller
+ * @author Dmitriy Kopylenko
* @since 1.1.1
* @see #setConfigLocation
* @see #setShared
@@ -60,12 +61,16 @@ public class EhCacheManagerFactoryBean implements FactoryBean, Ini
private Resource configLocation;
- private boolean shared = false;
-
private String cacheManagerName;
+ private boolean acceptExisting = false;
+
+ private boolean shared = false;
+
private CacheManager cacheManager;
+ private boolean locallyManaged = true;
+
/**
* Set the location of the EhCache config file. A typical value is "/WEB-INF/ehcache.xml".
@@ -78,17 +83,6 @@ public class EhCacheManagerFactoryBean implements FactoryBean, Ini
this.configLocation = configLocation;
}
- /**
- * Set whether the EhCache CacheManager should be shared (as a singleton at the VM level)
- * or independent (typically local within the application). Default is "false", creating
- * an independent instance.
- * @see net.sf.ehcache.CacheManager#create()
- * @see net.sf.ehcache.CacheManager#CacheManager()
- */
- public void setShared(boolean shared) {
- this.shared = shared;
- }
-
/**
* Set the name of the EhCache CacheManager (if a specific name is desired).
* @see net.sf.ehcache.CacheManager#setName(String)
@@ -97,6 +91,45 @@ public class EhCacheManagerFactoryBean implements FactoryBean, Ini
this.cacheManagerName = cacheManagerName;
}
+ /**
+ * Set whether an existing EhCache CacheManager of the same name will be accepted
+ * for this EhCacheManagerFactoryBean setup. Default is "false".
+ * Typically used in combination with {@link #setCacheManagerName "cacheManagerName"}
+ * but will simply work with the default CacheManager name if none specified.
+ * All references to the same CacheManager name (or the same default) in the
+ * same ClassLoader space will share the specified CacheManager then.
+ *
NOTE: This feature requires EhCache 2.5 or higher. In contrast to
+ * the {@link #setShared "shared"} flag, it supports controlled shutdown of the
+ * CacheManager by the EhCacheManagerFactoryBean that actually created it.
+ * @see #setCacheManagerName
+ * #see #setShared
+ * @see net.sf.ehcache.CacheManager#getCacheManager(String)
+ * @see net.sf.ehcache.CacheManager#CacheManager()
+ */
+ public void setAcceptExisting(boolean acceptExisting) {
+ this.acceptExisting = acceptExisting;
+ }
+
+ /**
+ * Set whether the EhCache CacheManager should be shared (as a singleton at the
+ * ClassLoader level) or independent (typically local within the application).
+ * Default is "false", creating an independent local instance.
+ *
NOTE: This feature allows for sharing this EhCacheManagerFactoryBean's
+ * CacheManager with any code calling CacheManager.create() in the same
+ * ClassLoader space, with no need to agree on a specific CacheManager name.
+ * However, it only supports a single EhCacheManagerFactoryBean involved which will
+ * control the lifecycle of the underlying CacheManager (in particular, its shutdown).
+ *
This flag overrides {@link #setAcceptExisting "acceptExisting"} if both are set,
+ * since it indicates the 'stronger' mode of sharing.
+ * @see #setCacheManagerName
+ * @see #setAcceptExisting
+ * @see net.sf.ehcache.CacheManager#create()
+ * @see net.sf.ehcache.CacheManager#CacheManager()
+ */
+ public void setShared(boolean shared) {
+ this.shared = shared;
+ }
+
@Override
public void afterPropertiesSet() throws IOException, CacheException {
@@ -108,7 +141,30 @@ public class EhCacheManagerFactoryBean implements FactoryBean, Ini
if (this.cacheManagerName != null) {
configuration.setName(this.cacheManagerName);
}
- this.cacheManager = (this.shared ? CacheManager.create(configuration) : new CacheManager(configuration));
+ if (this.shared) {
+ // Old-school EhCache singleton sharing...
+ // No way to find out whether we actually created a new CacheManager
+ // or just received an existing singleton reference.
+ this.cacheManager = CacheManager.create(configuration);
+ }
+ else if (this.acceptExisting) {
+ // EhCache 2.5+: Reusing an existing CacheManager of the same name.
+ // Basically the same code as in CacheManager.getInstance(String),
+ // just storing whether we're dealing with an existing instance.
+ synchronized (CacheManager.class) {
+ this.cacheManager = CacheManager.getCacheManager(this.cacheManagerName);
+ if (this.cacheManager == null) {
+ this.cacheManager = new CacheManager(configuration);
+ }
+ else {
+ this.locallyManaged = false;
+ }
+ }
+ }
+ else {
+ // Throwing an exception if a CacheManager of the same name exists already...
+ this.cacheManager = new CacheManager(configuration);
+ }
}
finally {
if (is != null) {
@@ -136,8 +192,10 @@ public class EhCacheManagerFactoryBean implements FactoryBean, Ini
@Override
public void destroy() {
- logger.info("Shutting down EhCache CacheManager");
- this.cacheManager.shutdown();
+ if (this.locallyManaged) {
+ logger.info("Shutting down EhCache CacheManager");
+ this.cacheManager.shutdown();
+ }
}
}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java
index 3d3c7ec99e..ffee20744a 100644
--- a/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java
+++ b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java
@@ -16,8 +16,8 @@
package org.springframework.cache.ehcache;
-import junit.framework.TestCase;
import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.config.CacheConfiguration;
@@ -26,17 +26,21 @@ import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;
import net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache;
+import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
+import static org.junit.Assert.*;
+
/**
- * @author Dmitriy Kopylenko
* @author Juergen Hoeller
+ * @author Dmitriy Kopylenko
* @since 27.09.2004
*/
-public class EhCacheSupportTests extends TestCase {
+public class EhCacheSupportTests {
- public void testLoadingBlankCacheManager() throws Exception {
+ @Test
+ public void testBlankCacheManager() throws Exception {
EhCacheManagerFactoryBean cacheManagerFb = new EhCacheManagerFactoryBean();
cacheManagerFb.setCacheManagerName("myCacheManager");
assertEquals(CacheManager.class, cacheManagerFb.getObjectType());
@@ -53,7 +57,59 @@ public class EhCacheSupportTests extends TestCase {
}
}
- public void testLoadingCacheManagerFromConfigFile() throws Exception {
+ @Test
+ public void testCacheManagerConflict() throws Exception {
+ EhCacheManagerFactoryBean cacheManagerFb = new EhCacheManagerFactoryBean();
+ cacheManagerFb.setCacheManagerName("myCacheManager");
+ assertEquals(CacheManager.class, cacheManagerFb.getObjectType());
+ assertTrue("Singleton property", cacheManagerFb.isSingleton());
+ cacheManagerFb.afterPropertiesSet();
+ try {
+ CacheManager cm = cacheManagerFb.getObject();
+ assertTrue("Loaded CacheManager with no caches", cm.getCacheNames().length == 0);
+ Cache myCache1 = cm.getCache("myCache1");
+ assertTrue("No myCache1 defined", myCache1 == null);
+
+ EhCacheManagerFactoryBean cacheManagerFb2 = new EhCacheManagerFactoryBean();
+ cacheManagerFb2.setCacheManagerName("myCacheManager");
+ cacheManagerFb2.afterPropertiesSet();
+ fail("Should have thrown CacheException because of naming conflict");
+ }
+ catch (CacheException ex) {
+ // expected
+ }
+ finally {
+ cacheManagerFb.destroy();
+ }
+ }
+
+ @Test
+ public void testAcceptExistingCacheManager() throws Exception {
+ EhCacheManagerFactoryBean cacheManagerFb = new EhCacheManagerFactoryBean();
+ cacheManagerFb.setCacheManagerName("myCacheManager");
+ assertEquals(CacheManager.class, cacheManagerFb.getObjectType());
+ assertTrue("Singleton property", cacheManagerFb.isSingleton());
+ cacheManagerFb.afterPropertiesSet();
+ try {
+ CacheManager cm = cacheManagerFb.getObject();
+ assertTrue("Loaded CacheManager with no caches", cm.getCacheNames().length == 0);
+ Cache myCache1 = cm.getCache("myCache1");
+ assertTrue("No myCache1 defined", myCache1 == null);
+
+ EhCacheManagerFactoryBean cacheManagerFb2 = new EhCacheManagerFactoryBean();
+ cacheManagerFb2.setCacheManagerName("myCacheManager");
+ cacheManagerFb2.setAcceptExisting(true);
+ cacheManagerFb2.afterPropertiesSet();
+ CacheManager cm2 = cacheManagerFb2.getObject();
+ assertSame(cm, cm2);
+ cacheManagerFb2.destroy();
+ }
+ finally {
+ cacheManagerFb.destroy();
+ }
+ }
+
+ public void testCacheManagerFromConfigFile() throws Exception {
EhCacheManagerFactoryBean cacheManagerFb = new EhCacheManagerFactoryBean();
cacheManagerFb.setConfigLocation(new ClassPathResource("testEhcache.xml", getClass()));
cacheManagerFb.setCacheManagerName("myCacheManager");
@@ -70,10 +126,12 @@ public class EhCacheSupportTests extends TestCase {
}
}
+ @Test
public void testEhCacheFactoryBeanWithDefaultCacheManager() throws Exception {
doTestEhCacheFactoryBean(false);
}
+ @Test
public void testEhCacheFactoryBeanWithExplicitCacheManager() throws Exception {
doTestEhCacheFactoryBean(true);
}
@@ -156,6 +214,7 @@ public class EhCacheSupportTests extends TestCase {
}
}
+ @Test
public void testEhCacheFactoryBeanWithBlockingCache() throws Exception {
EhCacheManagerFactoryBean cacheManagerFb = new EhCacheManagerFactoryBean();
cacheManagerFb.afterPropertiesSet();
@@ -175,6 +234,7 @@ public class EhCacheSupportTests extends TestCase {
}
}
+ @Test
public void testEhCacheFactoryBeanWithSelfPopulatingCache() throws Exception {
EhCacheManagerFactoryBean cacheManagerFb = new EhCacheManagerFactoryBean();
cacheManagerFb.afterPropertiesSet();
@@ -200,6 +260,7 @@ public class EhCacheSupportTests extends TestCase {
}
}
+ @Test
public void testEhCacheFactoryBeanWithUpdatingSelfPopulatingCache() throws Exception {
EhCacheManagerFactoryBean cacheManagerFb = new EhCacheManagerFactoryBean();
cacheManagerFb.afterPropertiesSet();