From 5febd497dad8309c11316fe15a8808459592077e Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 24 Feb 2016 11:38:05 +0100 Subject: [PATCH] Support customization of CacheManager This commit allows to customize the auto-configured `CacheManager` by exposing a bean of type `CacheManagerCustomizer`. The implementation may reference the type of a `CacheManager` to determine in which case it has to be invoked. Several implementations can be provided and ordered using the regular `Ordered` interface and `@Order` annotation. Closes gh-5039 --- .../cache/CacheAutoConfiguration.java | 2 +- .../cache/CacheManagerCustomizer.java | 36 ++++ .../cache/CacheManagerCustomizerInvoker.java | 84 ++++++++++ .../cache/EhCacheCacheConfiguration.java | 7 +- .../cache/GenericCacheConfiguration.java | 7 +- .../cache/GuavaCacheConfiguration.java | 6 +- .../cache/HazelcastInstanceConfiguration.java | 14 +- .../cache/InfinispanCacheConfiguration.java | 9 +- .../cache/JCacheCacheConfiguration.java | 9 +- .../cache/RedisCacheConfiguration.java | 6 +- .../cache/SimpleCacheConfiguration.java | 6 +- .../cache/CacheAutoConfigurationTests.java | 157 +++++++++++++++++- .../CacheManagerCustomizerInvokerTests.java | 96 +++++++++++ .../main/asciidoc/spring-boot-features.adoc | 23 +++ 14 files changed, 445 insertions(+), 17 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizer.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizerInvoker.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizerInvokerTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java index 0a1c1e58d4..cff8a0dd1f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java @@ -66,7 +66,7 @@ import org.springframework.util.Assert; @EnableConfigurationProperties(CacheProperties.class) @AutoConfigureBefore(HibernateJpaAutoConfiguration.class) @AutoConfigureAfter({ HazelcastAutoConfiguration.class, RedisAutoConfiguration.class }) -@Import(CacheConfigurationImportSelector.class) +@Import({ CacheManagerCustomizerInvoker.class, CacheConfigurationImportSelector.class }) public class CacheAutoConfiguration { static final String VALIDATOR_BEAN_NAME = "cacheAutoConfigurationValidator"; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizer.java new file mode 100644 index 0000000000..9ea7c52648 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2016 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 org.springframework.cache.CacheManager; + +/** + * Callback interface that can be implemented by beans wishing to customize the cache + * manager before it is fully initialized, in particular to tune its configuration. + * + * @author Stephane Nicoll + * @since 1.3.3 + */ +public interface CacheManagerCustomizer { + + /** + * Customize the cache manager. + * @param cacheManager the {@code CacheManager} to customize + */ + void customize(C cacheManager); + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizerInvoker.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizerInvoker.java new file mode 100644 index 0000000000..13f03a1738 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizerInvoker.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2016 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.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.cache.CacheManager; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; + +/** + * Invoke the available {@link CacheManagerCustomizer} instances in the context for a + * given {@link CacheManager}. + * + * @author Stephane Nicoll + */ +class CacheManagerCustomizerInvoker implements ApplicationContextAware { + + private ConfigurableApplicationContext applicationContext; + + /** + * Customize the specified {@link CacheManager}. Locates all {@link CacheManagerCustomizer} + * beans able to handle the specified instance and invoke + * {@link CacheManagerCustomizer#customize(CacheManager)} on them. + * @param cacheManager the cache manager to customize + */ + public void customize(CacheManager cacheManager) { + List> customizers = findCustomizers(cacheManager); + AnnotationAwareOrderComparator.sort(customizers); + for (CacheManagerCustomizer customizer : customizers) { + customizer.customize(cacheManager); + } + } + + @SuppressWarnings("unchecked") + private List> findCustomizers(CacheManager cacheManager) { + if (this.applicationContext == null) { + return Collections.emptyList(); + } + Map map = BeanFactoryUtils + .beansOfTypeIncludingAncestors(this.applicationContext.getBeanFactory(), CacheManagerCustomizer.class); + List> customizers + = new ArrayList>(); + for (CacheManagerCustomizer customizer : map.values()) { + Class target = GenericTypeResolver.resolveTypeArgument( + customizer.getClass(), CacheManagerCustomizer.class); + if (target == null || target.isAssignableFrom(cacheManager.getClass())) { + customizers.add(customizer); + } + } + return customizers; + } + + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (applicationContext instanceof ConfigurableApplicationContext) { + this.applicationContext = (ConfigurableApplicationContext) applicationContext; + } + } + +} 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 14a02b4628..a76b0fd470 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 @@ -48,9 +48,14 @@ class EhCacheCacheConfiguration { @Autowired private CacheProperties cacheProperties; + @Autowired + CacheManagerCustomizerInvoker customizerInvoker; + @Bean public EhCacheCacheManager cacheManager(CacheManager ehCacheCacheManager) { - return new EhCacheCacheManager(ehCacheCacheManager); + EhCacheCacheManager cacheManager = new EhCacheCacheManager(ehCacheCacheManager); + this.customizerInvoker.customize(cacheManager); + return cacheManager; } @Bean diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.java index 244122e2ef..052f3a55f4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.cache; import java.util.Collection; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cache.Cache; @@ -40,10 +41,14 @@ import org.springframework.context.annotation.Configuration; @Conditional(CacheCondition.class) class GenericCacheConfiguration { + @Autowired + CacheManagerCustomizerInvoker customizerInvoker; + @Bean public SimpleCacheManager cacheManager(Collection caches) { SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(caches); + this.customizerInvoker.customize(cacheManager); return cacheManager; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GuavaCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GuavaCacheConfiguration.java index 2973e881f0..5bb17c9ab0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GuavaCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GuavaCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -48,6 +48,9 @@ class GuavaCacheConfiguration { @Autowired private CacheProperties cacheProperties; + @Autowired + CacheManagerCustomizerInvoker customizerInvoker; + @Autowired(required = false) private CacheBuilder cacheBuilder; @@ -64,6 +67,7 @@ class GuavaCacheConfiguration { if (!CollectionUtils.isEmpty(cacheNames)) { cacheManager.setCacheNames(cacheNames); } + this.customizerInvoker.customize(cacheManager); return cacheManager; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastInstanceConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastInstanceConfiguration.java index c229d01829..a15ebbb61e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastInstanceConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/HazelcastInstanceConfiguration.java @@ -48,6 +48,9 @@ abstract class HazelcastInstanceConfiguration { @Autowired private CacheProperties cacheProperties; + @Autowired + CacheManagerCustomizerInvoker customizerInvoker; + @Bean public HazelcastCacheManager cacheManager( HazelcastInstance existingHazelcastInstance) throws IOException { @@ -58,7 +61,9 @@ abstract class HazelcastInstanceConfiguration { location).getHazelcastInstance(); return new CloseableHazelcastCacheManager(cacheHazelcastInstance); } - return new HazelcastCacheManager(existingHazelcastInstance); + HazelcastCacheManager cacheManager = new HazelcastCacheManager(existingHazelcastInstance); + this.customizerInvoker.customize(cacheManager); + return cacheManager; } } @@ -70,6 +75,9 @@ abstract class HazelcastInstanceConfiguration { @Autowired private CacheProperties cacheProperties; + @Autowired + CacheManagerCustomizerInvoker customizerInvoker; + @Bean public HazelcastInstance hazelcastInstance() throws IOException { Resource config = this.cacheProperties.getHazelcast().getConfig(); @@ -82,7 +90,9 @@ abstract class HazelcastInstanceConfiguration { @Bean public HazelcastCacheManager cacheManager() throws IOException { - return new HazelcastCacheManager(hazelcastInstance()); + HazelcastCacheManager cacheManager = new HazelcastCacheManager(hazelcastInstance()); + this.customizerInvoker.customize(cacheManager); + return cacheManager; } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/InfinispanCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/InfinispanCacheConfiguration.java index da0040aa56..8af9287770 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/InfinispanCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/InfinispanCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -51,13 +51,18 @@ public class InfinispanCacheConfiguration { @Autowired private CacheProperties cacheProperties; + @Autowired + CacheManagerCustomizerInvoker customizerInvoker; + @Autowired(required = false) private ConfigurationBuilder defaultConfigurationBuilder; @Bean public SpringEmbeddedCacheManager cacheManager( EmbeddedCacheManager embeddedCacheManager) { - return new SpringEmbeddedCacheManager(embeddedCacheManager); + SpringEmbeddedCacheManager cacheManager = new SpringEmbeddedCacheManager(embeddedCacheManager); + this.customizerInvoker.customize(cacheManager); + return cacheManager; } @Bean(destroyMethod = "stop") 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 d8653abedb..0d44a30caf 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -63,6 +63,9 @@ class JCacheCacheConfiguration { @Autowired private CacheProperties cacheProperties; + @Autowired + CacheManagerCustomizerInvoker customizerInvoker; + @Autowired(required = false) private javax.cache.configuration.Configuration defaultCacheConfiguration; @@ -71,7 +74,9 @@ class JCacheCacheConfiguration { @Bean public JCacheCacheManager cacheManager(CacheManager jCacheCacheManager) { - return new JCacheCacheManager(jCacheCacheManager); + JCacheCacheManager cacheManager = new JCacheCacheManager(jCacheCacheManager); + this.customizerInvoker.customize(cacheManager); + return cacheManager; } @Bean diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/RedisCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/RedisCacheConfiguration.java index 9e370f9668..56c1bb6f6d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/RedisCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/RedisCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -46,6 +46,9 @@ class RedisCacheConfiguration { @Autowired private CacheProperties cacheProperties; + @Autowired + CacheManagerCustomizerInvoker customizerInvoker; + @Bean public RedisCacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); @@ -53,6 +56,7 @@ class RedisCacheConfiguration { if (!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } + this.customizerInvoker.customize(cacheManager); return cacheManager; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.java index 4350ee73bd..419094a96a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -40,6 +40,9 @@ class SimpleCacheConfiguration { @Autowired private CacheProperties cacheProperties; + @Autowired + CacheManagerCustomizerInvoker customizerInvoker; + @Bean public ConcurrentMapCacheManager cacheManager() { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); @@ -47,6 +50,7 @@ class SimpleCacheConfiguration { if (!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } + this.customizerInvoker.customize(cacheManager); return cacheManager; } 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 3236de7453..6b66a2200f 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 @@ -17,8 +17,12 @@ package org.springframework.boot.autoconfigure.cache; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.Map; import javax.cache.configuration.CompleteConfiguration; import javax.cache.configuration.MutableConfiguration; @@ -70,6 +74,7 @@ import org.springframework.data.redis.core.RedisTemplate; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -150,6 +155,12 @@ public class CacheAutoConfigurationTests { assertThat(cacheManager.getCacheNames(), empty()); } + @Test + public void simpleCacheWithCustomizers() { + testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "simple", + "allCacheManagerCustomizer", "simpleCacheManagerCustomizer"); + } + @Test public void simpleCacheExplicitWithCacheNames() { load(DefaultCacheConfiguration.class, "spring.cache.type=simple", @@ -179,6 +190,12 @@ public class CacheAutoConfigurationTests { load(DefaultCacheConfiguration.class, "spring.cache.type=generic"); } + @Test + public void genericCacheWithCustomizers() { + testCustomizers(GenericCacheAndCustomizersConfiguration.class, "generic", + "allCacheManagerCustomizer", "genericCacheManagerCustomizer"); + } + @Test public void genericCacheExplicitWithCaches() { load(GenericCacheConfiguration.class, "spring.cache.type=generic"); @@ -197,6 +214,12 @@ public class CacheAutoConfigurationTests { assertThat(cacheManager.getCacheNames(), empty()); } + @Test + public void redisCacheWithCustomizers() { + testCustomizers(RedisCacheAndCustomizersConfiguration.class, "redis", + "allCacheManagerCustomizer", "redisCacheManagerCustomizer"); + } + @Test public void redisCacheExplicitWithCaches() { load(RedisCacheConfiguration.class, "spring.cache.type=redis", @@ -315,6 +338,12 @@ public class CacheAutoConfigurationTests { equalTo(cacheManager.getCacheManager())); } + @Test + public void ehCacheCacheWithCustomizers() { + testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "ehcache", + "allCacheManagerCustomizer", "ehCacheCacheManagerCustomizer"); + } + @Test public void ehCacheCacheWithConfig() { load(DefaultCacheConfiguration.class, "spring.cache.type=ehcache", @@ -348,6 +377,12 @@ public class CacheAutoConfigurationTests { equalTo(getHazelcastInstance(cacheManager))); } + @Test + public void hazelcastCacheWithCustomizers() { + testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "hazelcast", + "allCacheManagerCustomizer", "hazelcastCacheManagerCustomizer"); + } + @Test public void hazelcastCacheWithConfig() throws IOException { load(DefaultCacheConfiguration.class, "spring.cache.type=hazelcast", @@ -461,6 +496,12 @@ public class CacheAutoConfigurationTests { assertThat(cacheManager.getCacheNames(), containsInAnyOrder("foo", "bar")); } + @Test + public void infinispanCacheWithCustomizers() { + testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "infinispan", + "allCacheManagerCustomizer", "infinispanCacheManagerCustomizer"); + } + @Test public void infinispanCacheWithCaches() { load(DefaultCacheConfiguration.class, "spring.cache.type=infinispan", @@ -533,6 +574,12 @@ public class CacheAutoConfigurationTests { assertThat(((GuavaCache) foo).getNativeCache().stats().missCount(), equalTo(0L)); } + @Test + public void guavaCacheWithCustomizers() { + testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "guava", + "allCacheManagerCustomizer", "guavaCacheManagerCustomizer"); + } + @Test public void guavaCacheExplicitWithSpec() { load(DefaultCacheConfiguration.class, "spring.cache.type=guava", @@ -563,6 +610,28 @@ public class CacheAutoConfigurationTests { return type.cast(cacheManager); } + private void testCustomizers(Class config, String cacheType, String... expectedCustomizerNames) { + load(config, "spring.cache.type=" + cacheType); + CacheManager cacheManager = validateCacheManager(CacheManager.class); + List expected = new ArrayList(); + expected.addAll(Arrays.asList(expectedCustomizerNames)); + Map map = + this.context.getBeansOfType(CacheManagerTestCustomizer.class); + for (Map.Entry entry : map.entrySet()) { + if (expected.contains(entry.getKey())) { + expected.remove(entry.getKey()); + assertThat("Customizer with name " + entry.getKey() + " should have been " + + "invoked", entry.getValue().cacheManager, sameInstance(cacheManager)); + } + else { + assertThat("Customizer with name " + entry.getKey() + " should not have been" + + "invoked", entry.getValue().cacheManager, nullValue()); + } + } + assertThat("The following customizers should have been invoked: " + expected, + expected, hasSize(0)); + } + private void load(Class config, String... environment) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(applicationContext, environment); @@ -588,6 +657,13 @@ public class CacheAutoConfigurationTests { } + @Configuration + @EnableCaching + @Import(CacheManagerCustomizersConfiguration.class) + static class DefaultCacheAndCustomizersConfiguration { + + } + @Configuration @EnableCaching static class GenericCacheConfiguration { @@ -604,6 +680,11 @@ public class CacheAutoConfigurationTests { } + @Configuration + @Import({GenericCacheConfiguration.class, CacheManagerCustomizersConfiguration.class}) + static class GenericCacheAndCustomizersConfiguration { + } + @Configuration @EnableCaching static class RedisCacheConfiguration { @@ -615,6 +696,12 @@ public class CacheAutoConfigurationTests { } + @Configuration + @Import({RedisCacheConfiguration.class, CacheManagerCustomizersConfiguration.class}) + static class RedisCacheAndCustomizersConfiguration { + + } + @Configuration @EnableCaching static class JCacheCustomConfiguration { @@ -688,8 +775,8 @@ public class CacheAutoConfigurationTests { } @Configuration - @ImportAutoConfiguration({ CacheAutoConfiguration.class, - HazelcastAutoConfiguration.class }) + @ImportAutoConfiguration({CacheAutoConfiguration.class, + HazelcastAutoConfiguration.class}) static class HazelcastAndCacheConfiguration { } @@ -708,7 +795,7 @@ public class CacheAutoConfigurationTests { } @Configuration - @Import({ GenericCacheConfiguration.class, RedisCacheConfiguration.class }) + @Import({GenericCacheConfiguration.class, RedisCacheConfiguration.class}) static class CustomCacheManagerConfiguration { @Bean @@ -719,7 +806,7 @@ public class CacheAutoConfigurationTests { } @Configuration - @Import({ GenericCacheConfiguration.class, RedisCacheConfiguration.class }) + @Import({GenericCacheConfiguration.class, RedisCacheConfiguration.class}) static class CustomCacheManagerFromSupportConfiguration extends CachingConfigurerSupport { @@ -744,7 +831,7 @@ public class CacheAutoConfigurationTests { } @Configuration - @Import({ GenericCacheConfiguration.class, RedisCacheConfiguration.class }) + @Import({GenericCacheConfiguration.class, RedisCacheConfiguration.class}) static class CustomCacheResolverConfiguration extends CachingConfigurerSupport { @Override @@ -765,4 +852,64 @@ public class CacheAutoConfigurationTests { } + @Configuration + static class CacheManagerCustomizersConfiguration { + + @Bean + public CacheManagerCustomizer allCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { + }; + } + + @Bean + public CacheManagerCustomizer simpleCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { + }; + } + + @Bean + public CacheManagerCustomizer genericCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { }; + } + + @Bean + public CacheManagerCustomizer redisCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { }; + } + + @Bean + public CacheManagerCustomizer ehCacheCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { }; + } + + @Bean + public CacheManagerCustomizer hazelcastCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { }; + } + + @Bean + public CacheManagerCustomizer infinispanCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { }; + } + + @Bean + public CacheManagerCustomizer guavaCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { }; + } + + } + + static abstract class CacheManagerTestCustomizer implements CacheManagerCustomizer { + + private C cacheManager; + + @Override + public void customize(C cacheManager) { + if (this.cacheManager != null) { + throw new IllegalStateException("Customized invoked twice"); + } + this.cacheManager = cacheManager; + } + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizerInvokerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizerInvokerTests.java new file mode 100644 index 0000000000..1749250ad6 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheManagerCustomizerInvokerTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2016 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.Arrays; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * + * @author Stephane Nicoll + */ +public class CacheManagerCustomizerInvokerTests { + + private AnnotationConfigApplicationContext context; + + @After + public void tearDown() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void customizeSimpleCacheManager() { + load(SimpleConfiguration.class, "spring.cache.type=simple"); + ConcurrentMapCacheManager cacheManager = this.context.getBean(ConcurrentMapCacheManager.class); + assertThat(cacheManager.getCacheNames(), containsInAnyOrder("one", "two")); + assertThat(cacheManager.getCacheNames(), hasSize(2)); + } + + @Test + public void customizeNoConfigurableApplicationContext() { + CacheManagerCustomizerInvoker invoker = new CacheManagerCustomizerInvoker(); + ApplicationContext context = mock(ApplicationContext.class); + invoker.setApplicationContext(context); + invoker.customize(mock(CacheManager.class)); + verifyZeroInteractions(context); + } + + + private void load(Class config, String... environment) { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(applicationContext, environment); + applicationContext.register(config); + applicationContext.register(CacheAutoConfiguration.class); + applicationContext.refresh(); + this.context = applicationContext; + } + + + @Configuration + @EnableCaching + static class SimpleConfiguration { + + @Bean + public CacheManagerCustomizer cacheManagerCustomizer() { + return new CacheManagerCustomizer() { + @Override + public void customize(ConcurrentMapCacheManager cacheManager) { + cacheManager.setCacheNames(Arrays.asList("one", "two")); + } + }; + } + } +} 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 c4ed4e971d..80e94fd57f 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3184,6 +3184,29 @@ Spring Boot tries to detect the following providers (in this order): It is also possible to _force_ the cache provider to use via the `spring.cache.type` property. +If the `CacheManager` is auto-configured by Spring Boot, you can further tune its +configuration before it is fully initialized by exposing a bean implementing the +`CacheManagerCustomizer` interface. The following set the cache names to use. + +[source,java,indent=0] +---- + @Bean + public CacheManagerCustomizer cacheManagerCustomizer() { + return new CacheManagerCustomizer() { + @Override + public void customize(ConcurrentMapCacheManager cacheManager) { + cacheManager.setCacheNames(Arrays.asList("one", "two")); + } + }; + } +---- + +[NOTE] +=== +In the example above, a `ConcurrentMapCacheManager` is expected to be configured. If that +is not the case, the customizer won't be invoked at all. You can have as many customizers +as you want and you can also order them as usual using `@Order` or `Ordered`. +=== [[boot-features-caching-provider-generic]]