diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastJCacheCustomizationConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastJCacheCustomizationConfiguration.java new file mode 100644 index 00000000000..8de78bb5103 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastJCacheCustomizationConfiguration.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.cache; + +import java.io.IOException; +import java.net.URI; +import java.util.Properties; + +import com.hazelcast.core.HazelcastInstance; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; + +/** + * JCache customization for Hazelcast. + * + * @author Stephane Nicoll + */ +@Configuration +@ConditionalOnClass(HazelcastInstance.class) +class HazelcastJCacheCustomizationConfiguration { + + @Bean + public HazelcastPropertiesCustomizer hazelcastPropertiesCustomizer( + ObjectProvider hazelcastInstance) { + return new HazelcastPropertiesCustomizer( + hazelcastInstance.getIfUnique()); + } + + private static class HazelcastPropertiesCustomizer + implements JCachePropertiesCustomizer { + + private final HazelcastInstance hazelcastInstance; + + HazelcastPropertiesCustomizer(HazelcastInstance hazelcastInstance) { + this.hazelcastInstance = hazelcastInstance; + } + + @Override + public void customize(CacheProperties cacheProperties, Properties properties) { + Resource configLocation = cacheProperties + .resolveConfigLocation(cacheProperties.getJcache().getConfig()); + if (configLocation != null) { + // Hazelcast does not use the URI as a mean to specify a custom config. + properties.setProperty("hazelcast.config.location", + toUri(configLocation).toString()); + } + else if (this.hazelcastInstance != null) { + String name = this.hazelcastInstance.getName(); + if (name != null) { + properties.setProperty("hazelcast.instance.name", name); + } + } + } + + private static URI toUri(Resource config) { + try { + return config.getURI(); + } + catch (IOException ex) { + throw new IllegalArgumentException( + "Could not get URI from " + config, ex); + } + } + + } + +} 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 907e65310d5..f72f1eebcbd 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 @@ -40,6 +40,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.Order; @@ -59,6 +60,7 @@ import org.springframework.util.StringUtils; @ConditionalOnMissingBean(org.springframework.cache.CacheManager.class) @Conditional({ CacheCondition.class, JCacheCacheConfiguration.JCacheAvailableCondition.class }) +@Import(HazelcastJCacheCustomizationConfiguration.class) class JCacheCacheConfiguration { private final CacheProperties cacheProperties; @@ -69,15 +71,19 @@ class JCacheCacheConfiguration { private final List cacheManagerCustomizers; + private final List cachePropertiesCustomizers; + JCacheCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizers, ObjectProvider> defaultCacheConfigurationProvider, - ObjectProvider> cacheManagerCustomizersProvider) { + ObjectProvider> cacheManagerCustomizersProvider, + ObjectProvider> cachePropertiesCustomizers) { this.cacheProperties = cacheProperties; this.customizers = customizers; this.defaultCacheConfiguration = defaultCacheConfigurationProvider .getIfAvailable(); this.cacheManagerCustomizers = cacheManagerCustomizersProvider.getIfAvailable(); + this.cachePropertiesCustomizers = cachePropertiesCustomizers.getIfAvailable(); } @Bean @@ -103,14 +109,14 @@ class JCacheCacheConfiguration { private CacheManager createCacheManager() throws IOException { CachingProvider cachingProvider = getCachingProvider( this.cacheProperties.getJcache().getProvider()); + Properties properties = createCacheManagerProperties(); Resource configLocation = this.cacheProperties .resolveConfigLocation(this.cacheProperties.getJcache().getConfig()); if (configLocation != null) { return cachingProvider.getCacheManager(configLocation.getURI(), - cachingProvider.getDefaultClassLoader(), - createCacheManagerProperties(configLocation)); + cachingProvider.getDefaultClassLoader(), properties); } - return cachingProvider.getCacheManager(); + return cachingProvider.getCacheManager(null, null, properties); } private CachingProvider getCachingProvider(String cachingProviderFqn) { @@ -120,12 +126,13 @@ class JCacheCacheConfiguration { return Caching.getCachingProvider(); } - private Properties createCacheManagerProperties(Resource configLocation) - throws IOException { + private Properties createCacheManagerProperties() { Properties properties = new Properties(); - // Hazelcast does not use the URI as a mean to specify a custom config. - properties.setProperty("hazelcast.config.location", - configLocation.getURI().toString()); + if (this.cachePropertiesCustomizers != null) { + for (JCachePropertiesCustomizer customizer : this.cachePropertiesCustomizers) { + customizer.customize(this.cacheProperties, properties); + } + } return properties; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCachePropertiesCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCachePropertiesCustomizer.java new file mode 100644 index 00000000000..dcb83025a54 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/JCachePropertiesCustomizer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.cache; + +import java.util.Properties; + +import javax.cache.CacheManager; +import javax.cache.spi.CachingProvider; + +/** + * Callback interface that can be implemented by beans wishing to customize the properties + * used by the {@link CachingProvider} to create the {@link CacheManager}. + * + * @author Stephane Nicoll + */ +interface JCachePropertiesCustomizer { + + /** + * Customize the properties. + * @param cacheProperties the cache properties + * @param properties the current properties + * @see CachingProvider#getCacheManager(java.net.URI, ClassLoader, Properties) + */ + void customize(CacheProperties cacheProperties, Properties properties); + +} 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 d2c5f12de95..d72a7f74072 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 @@ -38,6 +38,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.CaffeineSpec; import com.google.common.cache.CacheBuilder; import com.hazelcast.cache.HazelcastCachingProvider; +import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.spring.cache.HazelcastCacheManager; import net.sf.ehcache.Status; @@ -53,7 +54,6 @@ 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.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.cache.support.MockCachingProvider; import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; import org.springframework.boot.test.util.EnvironmentTestUtils; @@ -433,8 +433,9 @@ public class CacheAutoConfigurationTests { } @Test - public void hazelcastCacheExplicit() { - load(DefaultCacheConfiguration.class, "spring.cache.type=hazelcast"); + public void hazelcastCacheExplicit() { // Fail + load(new Class[] { HazelcastAutoConfiguration.class, + DefaultCacheConfiguration.class }, "spring.cache.type=hazelcast"); HazelcastCacheManager cacheManager = validateCacheManager( HazelcastCacheManager.class); // NOTE: the hazelcast implementation knows about a cache in a lazy manner. @@ -446,7 +447,7 @@ public class CacheAutoConfigurationTests { @Test public void hazelcastCacheWithCustomizers() { - testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "hazelcast", + testCustomizers(HazelcastCacheAndCustomizersConfiguration.class, "hazelcast", "allCacheManagerCustomizer", "hazelcastCacheManagerCustomizer"); } @@ -486,15 +487,11 @@ public class CacheAutoConfigurationTests { } @Test - public void hazelcastCacheWithMainHazelcastAutoConfiguration() throws IOException { + public void hazelcastCacheWithHazelcastAutoConfiguration() throws IOException { String mainConfig = "org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml"; - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); - EnvironmentTestUtils.addEnvironment(applicationContext, - "spring.cache.type=hazelcast", "spring.hazelcast.config=" + mainConfig); - applicationContext.register(DefaultCacheConfiguration.class); - applicationContext.register(HazelcastAndCacheConfiguration.class); - applicationContext.refresh(); - this.context = applicationContext; + load(new Class[] { HazelcastAutoConfiguration.class, + DefaultCacheConfiguration.class }, "spring.cache.type=hazelcast", + "spring.hazelcast.config=" + mainConfig); HazelcastCacheManager cacheManager = validateCacheManager( HazelcastCacheManager.class); HazelcastInstance hazelcastInstance = this.context @@ -502,6 +499,8 @@ public class CacheAutoConfigurationTests { assertThat(getHazelcastInstance(cacheManager)).isEqualTo(hazelcastInstance); assertThat(hazelcastInstance.getConfig().getConfigurationFile()) .isEqualTo(new ClassPathResource(mainConfig).getFile()); + assertThat(cacheManager.getCache("foobar")).isNotNull(); + assertThat(cacheManager.getCacheNames()).containsOnly("foobar"); } @Test @@ -509,15 +508,10 @@ public class CacheAutoConfigurationTests { throws IOException { String mainConfig = "org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml"; String cacheConfig = "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml"; - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); - EnvironmentTestUtils.addEnvironment(applicationContext, - "spring.cache.type=hazelcast", + load(new Class[] { HazelcastAutoConfiguration.class, + DefaultCacheConfiguration.class }, "spring.cache.type=hazelcast", "spring.cache.hazelcast.config=" + cacheConfig, "spring.hazelcast.config=" + mainConfig); - applicationContext.register(DefaultCacheConfiguration.class); - applicationContext.register(HazelcastAndCacheConfiguration.class); - applicationContext.refresh(); - this.context = applicationContext; HazelcastInstance hazelcastInstance = this.context .getBean(HazelcastInstance.class); HazelcastCacheManager cacheManager = validateCacheManager( @@ -541,6 +535,7 @@ public class CacheAutoConfigurationTests { JCacheCacheManager cacheManager = validateCacheManager( JCacheCacheManager.class); assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar"); + assertThat(Hazelcast.getAllHazelcastInstances()).hasSize(1); } finally { Caching.getCachingProvider(cachingProviderFqn).close(); @@ -550,15 +545,65 @@ public class CacheAutoConfigurationTests { @Test public void hazelcastAsJCacheWithConfig() throws IOException { String cachingProviderFqn = HazelcastCachingProvider.class.getName(); - String configLocation = "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml"; - load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn, - "spring.cache.jcache.config=" + configLocation); - JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); + try { + String configLocation = "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml"; + load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.jcache.config=" + configLocation); + JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); - Resource configResource = new ClassPathResource(configLocation); + Resource configResource = new ClassPathResource(configLocation); + assertThat(cacheManager.getCacheManager().getURI()) + .isEqualTo(configResource.getURI()); + assertThat(Hazelcast.getAllHazelcastInstances()).hasSize(1); + } + finally { + Caching.getCachingProvider(cachingProviderFqn).close(); + } + } + + @Test + public void hazelcastAsJCacheWithExistingHazelcastInstance() throws IOException { + String cachingProviderFqn = HazelcastCachingProvider.class.getName(); + load(new Class[] { HazelcastAutoConfiguration.class, + DefaultCacheConfiguration.class }, "spring.cache.type=jcache", + "spring.cache.jcache.provider=" + cachingProviderFqn); + JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); + javax.cache.CacheManager jCacheManager = cacheManager.getCacheManager(); + assertThat(jCacheManager).isInstanceOf( + com.hazelcast.cache.HazelcastCacheManager.class); + assertThat(this.context.getBeansOfType(HazelcastInstance.class)).hasSize(1); + HazelcastInstance hazelcastInstance = this.context.getBean(HazelcastInstance.class); + assertThat(((com.hazelcast.cache.HazelcastCacheManager) jCacheManager) + .getHazelcastInstance()).isSameAs(hazelcastInstance); + assertThat(hazelcastInstance.getName()).isEqualTo("default-instance"); + assertThat(Hazelcast.getAllHazelcastInstances()).hasSize(1); + } + + @Test + public void hazelcastAsJCacheWithMainHazelcastAutoConfigurationAndSeparateCacheConfig() + throws IOException { + String cachingProviderFqn = HazelcastCachingProvider.class.getName(); + String mainConfig = "org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.xml"; + String cacheConfig = "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml"; + load(new Class[] { HazelcastAutoConfiguration.class, + DefaultCacheConfiguration.class }, "spring.cache.type=jcache", + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.jcache.config=" + cacheConfig, + "spring.hazelcast.config=" + mainConfig); + JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); + javax.cache.CacheManager jCacheManager = cacheManager.getCacheManager(); + assertThat(jCacheManager).isInstanceOf( + com.hazelcast.cache.HazelcastCacheManager.class); + assertThat(this.context.getBeansOfType(HazelcastInstance.class)).hasSize(1); + HazelcastInstance hazelcastInstance = this.context.getBean(HazelcastInstance.class); + assertThat(((com.hazelcast.cache.HazelcastCacheManager) jCacheManager) + .getHazelcastInstance()).isNotSameAs(hazelcastInstance); + assertThat(hazelcastInstance.getConfig().getConfigurationFile()) + .isEqualTo(new ClassPathResource(mainConfig).getFile()); assertThat(cacheManager.getCacheManager().getURI()) - .isEqualTo(configResource.getURI()); + .isEqualTo(new ClassPathResource(cacheConfig).getURI()); + assertThat(Hazelcast.getAllHazelcastInstances()).hasSize(2); } @Test @@ -622,14 +667,19 @@ public class CacheAutoConfigurationTests { } @Test - public void jCacheCacheWithCachesAndCustomizer() { + public void jCacheCacheWithCachesAndCustomizer() { // this one String cachingProviderFqn = HazelcastCachingProvider.class.getName(); - 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); - // see customizer - assertThat(cacheManager.getCacheNames()).containsOnly("foo", "custom1"); + try { + 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); + // see customizer + assertThat(cacheManager.getCacheNames()).containsOnly("foo", "custom1"); + } + finally { + Caching.getCachingProvider(cachingProviderFqn).close(); + } } @Test @@ -752,9 +802,13 @@ public class CacheAutoConfigurationTests { } private void load(Class config, String... environment) { + load(new Class[] { config }, environment); + } + + private void load(Class[] configs, String... environment) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(applicationContext, environment); - applicationContext.register(config); + applicationContext.register(configs); applicationContext.register(CacheAutoConfiguration.class); applicationContext.refresh(); this.context = applicationContext; @@ -807,6 +861,14 @@ public class CacheAutoConfigurationTests { } + @Configuration + @EnableCaching + @Import({ HazelcastAutoConfiguration.class, + CacheManagerCustomizersConfiguration.class }) + static class HazelcastCacheAndCustomizersConfiguration { + + } + @Configuration @EnableCaching static class CouchbaseCacheConfiguration { @@ -917,13 +979,6 @@ public class CacheAutoConfigurationTests { } - @Configuration - @ImportAutoConfiguration({ CacheAutoConfiguration.class, - HazelcastAutoConfiguration.class }) - static class HazelcastAndCacheConfiguration { - - } - @Configuration @EnableCaching static class InfinispanCustomConfiguration { diff --git a/spring-boot-autoconfigure/src/test/resources/hazelcast.xml b/spring-boot-autoconfigure/src/test/resources/hazelcast.xml index c07092f0d1e..4fe3006b2e0 100644 --- a/spring-boot-autoconfigure/src/test/resources/hazelcast.xml +++ b/spring-boot-autoconfigure/src/test/resources/hazelcast.xml @@ -3,6 +3,8 @@ xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + default-instance + diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 036501954a1..50b13c9e318 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3984,6 +3984,10 @@ NOTE: Since a cache library may offer both a native implementation and JSR-107 s Spring Boot will prefer the JSR-107 support so that the same features are available if you switch to a different JSR-107 implementation. +TIP: Spring Boot has a <>t. If +a single `HazelcastInstance` is available, it is automatically reused for the +`CacheManager` as well unless the `spring.cache.jcache.config` property is specified. + There are several ways to customize the underlying `javax.cache.cacheManager`: * Caches can be created on startup via the `spring.cache.cache-names` property. If a