From 1b3efd41f5a617b176fc278ac290b6d0dbe82f5e Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 8 May 2015 17:47:00 +0900 Subject: [PATCH] Expose caching-specific infrastructure Expose the underlying cache infrastructure bean if Boot auto-configures it. This is the case for ehCache, hazelcast and JCache. This change has two side effects: 1. It is now possible to customize the underlying cache infrastructure and let Boot only wrap it in the Spring's CacheManager abstraction. No customizations are applied if the caching-specific service is customized 2. Such infrastructure is disposed when the application terminates as it is now defined as `@Bean` and both `close()` and `shutdown()` methods are invoked if present on the target type. While the latter can be troublesome, we feel that a particular cache instance is not meant to be shared and must be disposed when the application terminates. Closes gh-2848 --- .../cache/EhCacheCacheConfiguration.java | 18 +- .../cache/HazelcastCacheConfiguration.java | 8 +- .../cache/JCacheCacheConfiguration.java | 52 +++-- .../cache/CacheAutoConfigurationTests.java | 177 ++++++++++-------- 4 files changed, 158 insertions(+), 97 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/EhCacheCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/EhCacheCacheConfiguration.java index 06aeadac6b3..fdc3e475bb5 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/EhCacheCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/EhCacheCacheConfiguration.java @@ -17,11 +17,11 @@ package org.springframework.boot.autoconfigure.cache; import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.cache.CacheManager; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.cache.ehcache.EhCacheManagerUtils; import org.springframework.context.annotation.Bean; @@ -34,11 +34,12 @@ import org.springframework.core.io.Resource; * a default configuration file exists. * * @author EddĂș MelĂ©ndez + * @author Stephane Nicoll * @since 1.3.0 */ @Configuration @ConditionalOnClass({ Cache.class, EhCacheCacheManager.class }) -@ConditionalOnMissingBean(CacheManager.class) +@ConditionalOnMissingBean(org.springframework.cache.CacheManager.class) @Conditional({ CacheCondition.class, EhCacheCacheConfiguration.ConfigAvailableCondition.class }) class EhCacheCacheConfiguration { @@ -47,13 +48,18 @@ class EhCacheCacheConfiguration { private CacheProperties properties; @Bean - public EhCacheCacheManager cacheManager() { + public EhCacheCacheManager cacheManager(CacheManager ehCacheCacheManager) { + return new EhCacheCacheManager(ehCacheCacheManager); + } + + @Bean + @ConditionalOnMissingBean + public CacheManager ehCacheCacheManager() { Resource location = this.properties.resolveConfigLocation(); if (location != null) { - return new EhCacheCacheManager( - EhCacheManagerUtils.buildCacheManager(location)); + return EhCacheManagerUtils.buildCacheManager(location); } - return new EhCacheCacheManager(EhCacheManagerUtils.buildCacheManager()); + return EhCacheManagerUtils.buildCacheManager(); } /** diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastCacheConfiguration.java index 425b91b6894..573190c908f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastCacheConfiguration.java @@ -57,11 +57,13 @@ class HazelcastCacheConfiguration { private CacheProperties cacheProperties; @Bean - public HazelcastCacheManager cacheManager() throws IOException { - return new HazelcastCacheManager(createHazelcastInstance()); + public HazelcastCacheManager cacheManager(HazelcastInstance hazelcastInstance) { + return new HazelcastCacheManager(hazelcastInstance); } - private HazelcastInstance createHazelcastInstance() throws IOException { + @Bean + @ConditionalOnMissingBean + public HazelcastInstance hazelcastInstance() throws IOException { Resource location = this.cacheProperties.resolveConfigLocation(); if (location != null) { Config cfg = new XmlConfigBuilder(location.getURL()).build(); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java index 1eb631bc1a0..d4a6a6fc41e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCacheCacheConfiguration.java @@ -27,9 +27,11 @@ import javax.cache.configuration.MutableConfiguration; import javax.cache.spi.CachingProvider; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.cache.jcache.JCacheCacheManager; @@ -68,19 +70,13 @@ class JCacheCacheConfiguration { private List cacheManagerCustomizers; @Bean - public JCacheCacheManager cacheManager() throws IOException { - CacheManager cacheManager = createCacheManager(); - List cacheNames = this.cacheProperties.getCacheNames(); - if (!CollectionUtils.isEmpty(cacheNames)) { - for (String cacheName : cacheNames) { - cacheManager.createCache(cacheName, getDefaultCacheConfiguration()); - } - } - customize(cacheManager); - return new JCacheCacheManager(cacheManager); + public JCacheCacheManager cacheManager(CacheManager jCacheCacheManager) { + return new JCacheCacheManager(jCacheCacheManager); } - private CacheManager createCacheManager() throws IOException { + @Bean + @ConditionalOnMissingBean + public CacheManager jCacheCacheManager() throws IOException { CachingProvider cachingProvider = getCachingProvider(this.cacheProperties .getJcache().getProvider()); Resource configLocation = this.cacheProperties.resolveConfigLocation(); @@ -89,7 +85,15 @@ class JCacheCacheConfiguration { cachingProvider.getDefaultClassLoader(), createCacheManagerProperties(configLocation)); } - return cachingProvider.getCacheManager(); + CacheManager jCacheCacheManager = cachingProvider.getCacheManager(); + List cacheNames = this.cacheProperties.getCacheNames(); + if (!CollectionUtils.isEmpty(cacheNames)) { + for (String cacheName : cacheNames) { + jCacheCacheManager.createCache(cacheName, getDefaultCacheConfiguration()); + } + } + customize(jCacheCacheManager); + return jCacheCacheManager; } private CachingProvider getCachingProvider(String cachingProviderFqn) { @@ -125,12 +129,32 @@ class JCacheCacheConfiguration { } /** - * Determines if JCache is available. This either kick in if a default + * Determine if JCache is available. This either kick in if a provider is available + * as defined per {@link JCacheProviderAvailableCondition} or if a {@link CacheManager} + * has already been defined. + */ + @Order(Ordered.LOWEST_PRECEDENCE) + static class JCacheAvailableCondition extends AnyNestedCondition { + + public JCacheAvailableCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @Conditional(JCacheProviderAvailableCondition.class) + static class JCacheProvider {} + + @ConditionalOnSingleCandidate(CacheManager.class) + static class CustomJCacheCacheManager {} + + } + + /** + * Determine if a JCache provider is available. This either kick in if a default * {@link CachingProvider} has been found or if the property referring to the provider * to use has been set. */ @Order(Ordered.LOWEST_PRECEDENCE) - static class JCacheAvailableCondition extends SpringBootCondition { + static class JCacheProviderAvailableCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java index e1a8c45361c..46785d356ad 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java @@ -19,16 +19,22 @@ package org.springframework.boot.autoconfigure.cache; import java.io.IOException; import java.util.Collection; import java.util.Collections; - import javax.cache.configuration.CompleteConfiguration; import javax.cache.configuration.MutableConfiguration; import javax.cache.expiry.CreatedExpiryPolicy; import javax.cache.expiry.Duration; +import com.google.common.cache.CacheBuilder; +import com.hazelcast.cache.HazelcastCachingProvider; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.spring.cache.HazelcastCacheManager; +import net.sf.ehcache.Status; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; + +import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.cache.support.MockCachingProvider; @@ -56,10 +62,6 @@ import org.springframework.core.io.Resource; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.core.RedisTemplate; -import com.google.common.cache.CacheBuilder; -import com.hazelcast.cache.HazelcastCachingProvider; -import com.hazelcast.spring.cache.HazelcastCacheManager; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.Matchers.contains; @@ -70,6 +72,7 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * Tests for {@link CacheAutoConfiguration}. @@ -213,6 +216,8 @@ public class CacheAutoConfigurationTests { "spring.cache.jcache.provider=" + cachingProviderFqn); JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); assertThat(cacheManager.getCacheNames(), is(empty())); + assertThat(this.context.getBean(javax.cache.CacheManager.class), + is(cacheManager.getCacheManager())); } @Test @@ -244,6 +249,13 @@ public class CacheAutoConfigurationTests { defaultCacheConfiguration); } + @Test + public void jCacheCacheWithExistingJCacheManager() { + load(JCacheCustomCacheManager.class, "spring.cache.type=jcache"); + JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); + assertThat(cacheManager.getCacheManager(), is(this.context.getBean("customJCacheCacheManager"))); + } + @Test public void jCacheCacheWithUnknownProvider() { String wrongCachingProviderFqn = "org.acme.FooBar"; @@ -280,36 +292,29 @@ public class CacheAutoConfigurationTests { @Test public void ehCacheCacheWithCaches() { load(DefaultCacheConfiguration.class, "spring.cache.type=ehcache"); - EhCacheCacheManager cacheManager = null; - try { - cacheManager = validateCacheManager(EhCacheCacheManager.class); - assertThat(cacheManager.getCacheNames(), - containsInAnyOrder("cacheTest1", "cacheTest2")); - assertThat(cacheManager.getCacheNames(), hasSize(2)); - } - finally { - if (cacheManager != null) { - cacheManager.getCacheManager().shutdown(); - } - } + EhCacheCacheManager cacheManager = validateCacheManager(EhCacheCacheManager.class); + assertThat(cacheManager.getCacheNames(), + containsInAnyOrder("cacheTest1", "cacheTest2")); + assertThat(cacheManager.getCacheNames(), hasSize(2)); + assertThat(this.context.getBean(net.sf.ehcache.CacheManager.class), + is(cacheManager.getCacheManager())); } @Test public void ehCacheCacheWithConfig() { load(DefaultCacheConfiguration.class, "spring.cache.type=ehcache", "spring.cache.config=cache/ehcache-override.xml"); - EhCacheCacheManager cacheManager = null; - try { - cacheManager = validateCacheManager(EhCacheCacheManager.class); - assertThat(cacheManager.getCacheNames(), - containsInAnyOrder("cacheOverrideTest1", "cacheOverrideTest2")); - assertThat(cacheManager.getCacheNames(), hasSize(2)); - } - finally { - if (cacheManager != null) { - cacheManager.getCacheManager().shutdown(); - } - } + EhCacheCacheManager cacheManager = validateCacheManager(EhCacheCacheManager.class); + assertThat(cacheManager.getCacheNames(), + containsInAnyOrder("cacheOverrideTest1", "cacheOverrideTest2")); + assertThat(cacheManager.getCacheNames(), hasSize(2)); + } + + @Test + public void ehCacheCacheWithExistingCacheManager() { + load(EhCacheCustomCacheManager.class, "spring.cache.type=ehcache"); + EhCacheCacheManager cacheManager = validateCacheManager(EhCacheCacheManager.class); + assertThat(cacheManager.getCacheManager(), is(this.context.getBean("customEhCacheCacheManager"))); } @Test @@ -320,6 +325,8 @@ public class CacheAutoConfigurationTests { cacheManager.getCache("defaultCache"); assertThat(cacheManager.getCacheNames(), containsInAnyOrder("defaultCache")); assertThat(cacheManager.getCacheNames(), hasSize(1)); + assertThat(this.context.getBean(HazelcastInstance.class), + is(new DirectFieldAccessor(cacheManager).getPropertyValue("hazelcastInstance"))); } @Test @@ -340,65 +347,49 @@ public class CacheAutoConfigurationTests { "spring.cache.config=foo/bar/unknown.xml"); } + @Test + public void hazelcastCacheWithExistingHazelcastInstance() { + load(HazelcastCustomHazelcastInstance.class, "spring.cache.type=hazelcast"); + HazelcastCacheManager cacheManager = validateCacheManager(HazelcastCacheManager.class); + assertThat(new DirectFieldAccessor(cacheManager).getPropertyValue("hazelcastInstance"), + is(this.context.getBean("customHazelcastInstance"))); + } + @Test public void hazelcastAsJCacheWithCaches() { String cachingProviderFqn = HazelcastCachingProvider.class.getName(); - JCacheCacheManager cacheManager = null; - try { - load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn, - "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); - cacheManager = validateCacheManager(JCacheCacheManager.class); - assertThat(cacheManager.getCacheNames(), containsInAnyOrder("foo", "bar")); - assertThat(cacheManager.getCacheNames(), hasSize(2)); - } - finally { - if (cacheManager != null) { - cacheManager.getCacheManager().close(); - } - } + load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); + JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); + assertThat(cacheManager.getCacheNames(), containsInAnyOrder("foo", "bar")); + assertThat(cacheManager.getCacheNames(), hasSize(2)); } @Test public void hazelcastAsJCacheWithConfig() throws IOException { String cachingProviderFqn = HazelcastCachingProvider.class.getName(); String configLocation = "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml"; - JCacheCacheManager cacheManager = null; - try { - load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn, - "spring.cache.config=" + configLocation); - cacheManager = validateCacheManager(JCacheCacheManager.class); - - Resource configResource = new ClassPathResource(configLocation); - assertThat(cacheManager.getCacheManager().getURI(), - is(configResource.getURI())); - } - finally { - if (cacheManager != null) { - cacheManager.getCacheManager().close(); - } - } + load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.config=" + configLocation); + JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); + + Resource configResource = new ClassPathResource(configLocation); + assertThat(cacheManager.getCacheManager().getURI(), + is(configResource.getURI())); } @Test public void jCacheCacheWithCachesAndCustomizer() { String cachingProviderFqn = HazelcastCachingProvider.class.getName(); - JCacheCacheManager cacheManager = null; - try { - load(JCacheWithCustomizerConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn, - "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); - cacheManager = validateCacheManager(JCacheCacheManager.class); - assertThat(cacheManager.getCacheNames(), containsInAnyOrder("foo", "custom1")); // see - // customizer - assertThat(cacheManager.getCacheNames(), hasSize(2)); - } - finally { - if (cacheManager != null) { - cacheManager.getCacheManager().close(); - } - } + load(JCacheWithCustomizerConfiguration.class, "spring.cache.type=jcache", + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); + JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); + assertThat(cacheManager.getCacheNames(), containsInAnyOrder("foo", "custom1")); // see + // customizer + assertThat(cacheManager.getCacheNames(), hasSize(2)); } @Test @@ -505,6 +496,19 @@ public class CacheAutoConfigurationTests { } + @Configuration + @EnableCaching + static class JCacheCustomCacheManager { + + @Bean + public javax.cache.CacheManager customJCacheCacheManager() { + javax.cache.CacheManager cacheManager = mock(javax.cache.CacheManager.class); + when(cacheManager.getCacheNames()).thenReturn(Collections.emptyList()); + return cacheManager; + } + + } + @Configuration @EnableCaching static class JCacheWithCustomizerConfiguration { @@ -526,6 +530,31 @@ public class CacheAutoConfigurationTests { } + @Configuration + @EnableCaching + static class EhCacheCustomCacheManager { + + @Bean + public net.sf.ehcache.CacheManager customEhCacheCacheManager() { + net.sf.ehcache.CacheManager cacheManager = mock(net.sf.ehcache.CacheManager.class); + when(cacheManager.getStatus()).thenReturn(Status.STATUS_ALIVE); + when(cacheManager.getCacheNames()).thenReturn(new String[0]); + return cacheManager; + } + + } + + @Configuration + @EnableCaching + static class HazelcastCustomHazelcastInstance { + + @Bean + public HazelcastInstance customHazelcastInstance() { + return mock(HazelcastInstance.class); + } + + } + @Configuration @Import({ GenericCacheConfiguration.class, RedisCacheConfiguration.class }) static class CustomCacheManagerConfiguration {