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();