diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfiguration.java index 6a4b184bde..99ee83f9ee 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfiguration.java @@ -76,17 +76,6 @@ public class CacheStatisticsAutoConfiguration { } - @Configuration - @ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class }) - static class CaffeineCacheStatisticsProviderConfiguration { - - @Bean - public CaffeineCacheStatisticsProvider caffeineCacheStatisticsProvider() { - return new CaffeineCacheStatisticsProvider(); - } - - } - @Configuration @ConditionalOnClass({ EhCacheCache.class, Ehcache.class, StatisticsGateway.class }) static class EhCacheCacheStatisticsProviderConfiguration { @@ -119,6 +108,17 @@ public class CacheStatisticsAutoConfiguration { } + @Configuration + @ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class }) + static class CaffeineCacheStatisticsProviderConfiguration { + + @Bean + public CaffeineCacheStatisticsProvider caffeineCacheStatisticsProvider() { + return new CaffeineCacheStatisticsProvider(); + } + + } + @Configuration @ConditionalOnClass({ com.google.common.cache.Cache.class, GuavaCache.class }) static class GuavaCacheStatisticsConfiguration { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfigurationTests.java index a6d59baf54..f933d904b1 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfigurationTests.java @@ -118,6 +118,14 @@ public class CacheStatisticsAutoConfigurationTests { doTestCoreStatistics(provider, true); } + @Test + public void baseCaffeineCacheStatistics() { + load(CaffeineCacheConfig.class); + CacheStatisticsProvider provider = this.context + .getBean("caffeineCacheStatisticsProvider", CacheStatisticsProvider.class); + doTestCoreStatistics(provider, true); + } + @Test public void concurrentMapCacheStatistics() { load(ConcurrentMapConfig.class); @@ -148,14 +156,6 @@ public class CacheStatisticsAutoConfigurationTests { assertCoreStatistics(updatedCacheStatistics, null, null, null); } - @Test - public void caffeineCacheStatistics() { - load(CaffeineCacheConfig.class); - CacheStatisticsProvider provider = this.context - .getBean("caffeineCacheStatisticsProvider", CacheStatisticsProvider.class); - doTestCoreStatistics(provider, true); - } - private void doTestCoreStatistics(CacheStatisticsProvider provider, boolean supportSize) { Cache books = getCache("books"); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CaffeineCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CaffeineCacheConfiguration.java index dc958fd896..a9f38e8c5b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CaffeineCacheConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CaffeineCacheConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.cache; +import java.util.List; + import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.CaffeineSpec; @@ -23,10 +25,12 @@ import com.github.benmanes.caffeine.cache.CaffeineSpec; 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.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** @@ -37,13 +41,16 @@ import org.springframework.util.StringUtils; */ @Configuration @ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class }) -@ConditionalOnMissingBean(org.springframework.cache.CacheManager.class) +@ConditionalOnMissingBean(CacheManager.class) @Conditional({ CacheCondition.class }) class CaffeineCacheConfiguration { @Autowired private CacheProperties cacheProperties; + @Autowired + private CacheManagerCustomizers customizers; + @Autowired(required = false) private Caffeine caffeine; @@ -54,27 +61,34 @@ class CaffeineCacheConfiguration { private CacheLoader cacheLoader; @Bean - @ConditionalOnMissingBean public CaffeineCacheManager caffeineCacheManager() { - CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager(); - setCacheBuilder(caffeineCacheManager); - if (this.cacheLoader != null) { - caffeineCacheManager.setCacheLoader(this.cacheLoader); + CaffeineCacheManager cacheManager = createCacheManager(); + List cacheNames = this.cacheProperties.getCacheNames(); + if (!CollectionUtils.isEmpty(cacheNames)) { + cacheManager.setCacheNames(cacheNames); } - caffeineCacheManager.setCacheNames(this.cacheProperties.getCacheNames()); - return caffeineCacheManager; + return this.customizers.customize(cacheManager); } - private void setCacheBuilder(CaffeineCacheManager caffeineCacheManager) { + private CaffeineCacheManager createCacheManager() { + CaffeineCacheManager cacheManager = new CaffeineCacheManager(); + setCacheBuilder(cacheManager); + if (this.cacheLoader != null) { + cacheManager.setCacheLoader(this.cacheLoader); + } + return cacheManager; + } + + private void setCacheBuilder(CaffeineCacheManager cacheManager) { String specification = this.cacheProperties.getCaffeine().getSpec(); if (StringUtils.hasText(specification)) { - caffeineCacheManager.setCaffeine(Caffeine.from(specification)); + cacheManager.setCacheSpecification(specification); } else if (this.caffeineSpec != null) { - caffeineCacheManager.setCaffeine(Caffeine.from(this.caffeineSpec)); + cacheManager.setCaffeineSpec(this.caffeineSpec); } else if (this.caffeine != null) { - caffeineCacheManager.setCaffeine(this.caffeine); + cacheManager.setCaffeine(this.caffeine); } } 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 f6bc1b46a8..c19bb16bec 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 @@ -584,48 +584,55 @@ public class CacheAutoConfigurationTests { validateGuavaCacheWithStats(); } - @Test - public void caffeineCacheWithCaches() { - load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine", - "spring.cache.cacheNames[0]=spring", "spring.cache.cacheNames[1]=boot"); - CaffeineCacheManager cacheManager = validateCacheManager( - CaffeineCacheManager.class); - assertThat(cacheManager.getCacheNames()).containsOnly("spring", "boot"); + private void validateGuavaCacheWithStats() { + GuavaCacheManager cacheManager = validateCacheManager(GuavaCacheManager.class); + assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar"); + Cache foo = cacheManager.getCache("foo"); + foo.get("1"); + assertThat(((GuavaCache) foo).getNativeCache().stats().missCount()).isEqualTo(1L); } @Test - public void caffeineCacheNotAllowNullValues() { + public void caffeineCacheWithExplicitCaches() { load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine", - "spring.cache.caffeine.allow-null-values=false"); + "spring.cache.cacheNames=foo"); CaffeineCacheManager cacheManager = validateCacheManager( CaffeineCacheManager.class); - assertThat(cacheManager.isAllowNullValues()).isFalse(); + assertThat(cacheManager.getCacheNames()).containsOnly("foo"); + Cache foo = cacheManager.getCache("foo"); + foo.get("1"); + // See next tests: no spec given so stats should be disabled + assertThat(((CaffeineCache) foo).getNativeCache().stats().missCount()).isEqualTo(0L); } @Test - public void caffeineCacheWithNullCaches() { + public void caffeineCacheWithCustomizers() { + testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "caffeine", + "allCacheManagerCustomizer", "caffeineCacheManagerCustomizer"); + } + + @Test + public void caffeineCacheWithExplicitCacheBuilder() { load(CaffeineCacheBuilderConfiguration.class, "spring.cache.type=caffeine", - "spring.cache.cacheNames[0]=caffeine", "spring.cache.cacheNames[1]=cache"); - CaffeineCacheManager cacheManager = validateCacheManager( - CaffeineCacheManager.class); - assertThat(cacheManager.isAllowNullValues()).isTrue(); - } - - @Test - public void caffeineCacheExplicitWithSpec() { - load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine", - "spring.cache.caffeine.spec=recordStats", "spring.cache.cacheNames[0]=foo", - "spring.cache.cacheNames[1]=bar"); + "spring.cache.cacheNames=foo,bar"); validateCaffeineCacheWithStats(); } @Test - public void caffeineCacheExplicitWithCacheBuilder() { - load(CaffeineCacheBuilderConfiguration2.class, "spring.cache.type=caffeine", + public void caffeineCacheExplicitWithSpec() { + load(CaffeineCacheSpecConfiguration.class, "spring.cache.type=caffeine", "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); validateCaffeineCacheWithStats(); } + @Test + public void caffeineCacheExplicitWithSpecString() { + load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine", + "spring.cache.caffeine.spec=recordStats", "spring.cache.cacheNames[0]=foo", + "spring.cache.cacheNames[1]=bar"); + validateCaffeineCacheWithStats(); + } + private void validateCaffeineCacheWithStats() { CaffeineCacheManager cacheManager = validateCacheManager(CaffeineCacheManager.class); assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar"); @@ -634,14 +641,6 @@ public class CacheAutoConfigurationTests { assertThat(((CaffeineCache) foo).getNativeCache().stats().missCount()).isEqualTo(1L); } - private void validateGuavaCacheWithStats() { - GuavaCacheManager cacheManager = validateCacheManager(GuavaCacheManager.class); - assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar"); - Cache foo = cacheManager.getCache("foo"); - foo.get("1"); - assertThat(((GuavaCache) foo).getNativeCache().stats().missCount()).isEqualTo(1L); - } - private T validateCacheManager(Class type) { CacheManager cacheManager = this.context.getBean(CacheManager.class); assertThat(cacheManager).as("Wrong cache manager type").isInstanceOf(type); @@ -897,14 +896,14 @@ public class CacheAutoConfigurationTests { @Bean Caffeine cacheBuilder() { - return Caffeine.newBuilder().maximumSize(10); + return Caffeine.newBuilder().recordStats(); } } @Configuration @EnableCaching - static class CaffeineCacheBuilderConfiguration2 { + static class CaffeineCacheSpecConfiguration { @Bean CaffeineSpec caffeineSpec() { @@ -964,6 +963,12 @@ public class CacheAutoConfigurationTests { }; } + @Bean + public CacheManagerCustomizer caffeineCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { + }; + } + } static abstract class CacheManagerTestCustomizer 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 e5c5b8a027..972bc82fd7 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3249,6 +3249,7 @@ Spring Boot tries to detect the following providers (in this order): * <> * <> * <> +* <> * <> * <> @@ -3384,6 +3385,31 @@ recommend to keep this setting enabled if you create your own `RedisCacheManager +[[boot-features-caching-provider-caffeine]] +==== Caffeine +Caffeine is a Java 8 rewrite of Guava’s cache and will supersede the Guava support in +Spring Boot 2.0. If Caffeine is present, a `CaffeineCacheManager` is auto-configured. +Caches can be created on startup using the `spring.cache.cache-names` property and +customized by one of the following (in this order): + +1. A cache spec defined by `spring.cache.caffeine.spec` +2. A `com.github.benmanes.caffeine.cache.CaffeineSpec` bean is defined +3. A `com.github.benmanes.caffeine.cache.Caffeine` bean is defined + +For instance, the following configuration creates a `foo` and `bar` caches with a maximum +size of 500 and a _time to live_ of 10 minutes + +[source,properties,indent=0] +---- + spring.cache.cache-names=foo,bar + spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s +---- + +Besides, if a `com.github.benmanes.caffeine.cache.CacheLoader` bean is defined, it is +automatically associated to the `CaffeineCacheManager`. + + + [[boot-features-caching-provider-guava]] ==== Guava If Guava is present, a `GuavaCacheManager` is auto-configured. Caches can be created diff --git a/spring-boot-samples/spring-boot-sample-cache/README.adoc b/spring-boot-samples/spring-boot-sample-cache/README.adoc index 5a44d740e4..7ac9474d0f 100644 --- a/spring-boot-samples/spring-boot-sample-cache/README.adoc +++ b/spring-boot-samples/spring-boot-sample-cache/README.adoc @@ -84,6 +84,13 @@ a redis instance with the default settings is expected on your local box). +=== Caffeine +Simply add the `com.github.ben-manes.caffeine:caffeine` dependency to enable support +for Caffeine. You can customize how caches are created in different ways, see +`application.properties` for an example and the documentation for more details. + + + === Guava Spring Boot does not provide any dependency management for _Guava_ so you'll have to add the `com.google.guava:guava` dependency with a version. You can customize how caches are diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application.properties index 6c25ae911a..80048ca64c 100644 --- a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application.properties @@ -10,6 +10,12 @@ #spring.cache.jcache.config=hazelcast.xml +# +# Caffeine configuration +# +#spring.cache.caffeine.spec=maximumSize=200,expireAfterAccess=600s + + # # Guava configuration #