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
This commit is contained in:
Stephane Nicoll 2015-05-08 17:47:00 +09:00
parent b20d11fe9c
commit 1b3efd41f5
4 changed files with 157 additions and 96 deletions

View File

@ -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();
}
/**

View File

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

View File

@ -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<JCacheManagerCustomizer> cacheManagerCustomizers;
@Bean
public JCacheCacheManager cacheManager() throws IOException {
CacheManager cacheManager = createCacheManager();
List<String> 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<String> 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,

View File

@ -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);
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()));
}
finally {
if (cacheManager != null) {
cacheManager.getCacheManager().close();
}
}
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.<String>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 {