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 0000000000..0b3deba443 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastJCacheCustomizationConfiguration.java @@ -0,0 +1,82 @@ +/* + * 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) { + properties.put("hazelcast.instance.itself", this.hazelcastInstance); + } + } + + 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 da7d62d095..4e76716d82 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,14 +71,18 @@ class JCacheCacheConfiguration { private final List cacheManagerCustomizers; + private final List cachePropertiesCustomizers; + JCacheCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizers, ObjectProvider> defaultCacheConfiguration, - ObjectProvider> cacheManagerCustomizers) { + ObjectProvider> cacheManagerCustomizers, + ObjectProvider> cachePropertiesCustomizers) { this.cacheProperties = cacheProperties; this.customizers = customizers; this.defaultCacheConfiguration = defaultCacheConfiguration.getIfAvailable(); this.cacheManagerCustomizers = cacheManagerCustomizers.getIfAvailable(); + this.cachePropertiesCustomizers = cachePropertiesCustomizers.getIfAvailable(); } @Bean @@ -102,14 +108,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) { @@ -119,12 +125,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 0000000000..dcb83025a5 --- /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 001b571269..e135374933 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 @@ -37,6 +37,7 @@ import com.couchbase.client.spring.cache.CouchbaseCacheManager; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.CaffeineSpec; 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; @@ -429,7 +430,7 @@ public class CacheAutoConfigurationTests { } @Test - public void hazelcastCacheExplicit() { + public void hazelcastCacheExplicit() { // Fail load(new Class[] { HazelcastAutoConfiguration.class, DefaultCacheConfiguration.class }, "spring.cache.type=hazelcast"); HazelcastCacheManager cacheManager = validateCacheManager( @@ -483,6 +484,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(); @@ -492,15 +494,39 @@ public class CacheAutoConfigurationTests { @Test public void hazelcastAsJCacheWithConfig() throws IOException { String cachingProviderFqn = HazelcastCachingProvider.class.getName(); - String configLocation = "org/springframework/boot/autoconfigure/hazelcast/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/hazelcast/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); - assertThat(cacheManager.getCacheManager().getURI()) - .isEqualTo(configResource.getURI()); + 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 @@ -566,12 +592,17 @@ public class CacheAutoConfigurationTests { @Test public void jCacheCacheWithCachesAndCustomizer() { 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 diff --git a/spring-boot-autoconfigure/src/test/resources/hazelcast.xml b/spring-boot-autoconfigure/src/test/resources/hazelcast.xml index 15665878ac..c59ae8a463 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 ae9edbe302..e5be29e1c0 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -4117,6 +4117,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