From 98cc68364a89df9b42f54f48337349b287fbe7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Thu, 7 Jan 2016 18:10:11 -0500 Subject: [PATCH 1/2] Add Caffeine cache support See gh-4899 --- spring-boot-actuator/pom.xml | 5 ++ .../CacheStatisticsAutoConfiguration.java | 17 +++- .../CaffeineCacheStatisticsProvider.java | 46 +++++++++++ ...CacheStatisticsAutoConfigurationTests.java | 24 ++++++ spring-boot-autoconfigure/pom.xml | 5 ++ .../cache/CacheConfigurations.java | 4 +- .../autoconfigure/cache/CacheProperties.java | 29 ++++++- .../boot/autoconfigure/cache/CacheType.java | 7 +- .../cache/CaffeineCacheConfiguration.java | 81 +++++++++++++++++++ .../cache/CacheAutoConfigurationTests.java | 76 +++++++++++++++++ spring-boot-dependencies/pom.xml | 6 ++ .../appendix-application-properties.adoc | 1 + 12 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/CaffeineCacheStatisticsProvider.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CaffeineCacheConfiguration.java diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 7aba9cd5d3..d4c7b547e5 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -61,6 +61,11 @@ hal-browser true + + com.github.ben-manes.caffeine + caffeine + true + com.google.guava guava 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 3ddd3fa3cc..6a4b184bde 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 @@ -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.actuate.autoconfigure; import javax.cache.Caching; +import com.github.benmanes.caffeine.cache.Caffeine; import com.hazelcast.core.IMap; import com.hazelcast.spring.cache.HazelcastCache; import net.sf.ehcache.Ehcache; @@ -26,6 +27,7 @@ import org.infinispan.spring.provider.SpringCache; import org.springframework.boot.actuate.cache.CacheStatistics; import org.springframework.boot.actuate.cache.CacheStatisticsProvider; +import org.springframework.boot.actuate.cache.CaffeineCacheStatisticsProvider; import org.springframework.boot.actuate.cache.ConcurrentMapCacheStatisticsProvider; import org.springframework.boot.actuate.cache.DefaultCacheStatistics; import org.springframework.boot.actuate.cache.EhCacheStatisticsProvider; @@ -40,6 +42,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; +import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.cache.ehcache.EhCacheCache; import org.springframework.cache.guava.GuavaCache; @@ -54,6 +57,7 @@ import org.springframework.context.annotation.Configuration; * * @author Stephane Nicoll * @author Phillip Webb + * @author Eddú Meléndez * @since 1.3.0 */ @Configuration @@ -72,6 +76,17 @@ 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 { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/CaffeineCacheStatisticsProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/CaffeineCacheStatisticsProvider.java new file mode 100644 index 0000000000..dc6e77ffe8 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/CaffeineCacheStatisticsProvider.java @@ -0,0 +1,46 @@ +/* + * 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.actuate.cache; + +import com.github.benmanes.caffeine.cache.stats.CacheStats; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.caffeine.CaffeineCache; + +/** + * {@link CacheStatisticsProvider} implementation for Caffeine. + * + * @author Eddú Meléndez + * @since 1.4.0 + */ +public class CaffeineCacheStatisticsProvider + implements CacheStatisticsProvider { + + @Override + public CacheStatistics getCacheStatistics(CacheManager cacheManager, + CaffeineCache cache) { + DefaultCacheStatistics statistics = new DefaultCacheStatistics(); + statistics.setSize(cache.getNativeCache().estimatedSize()); + CacheStats caffeineStatistics = cache.getNativeCache().stats(); + if (caffeineStatistics.requestCount() > 0) { + statistics.setHitRatio(caffeineStatistics.hitRate()); + statistics.setMissRatio(caffeineStatistics.missRate()); + } + return statistics; + } + +} 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 ce87b8f5f7..a6d59baf54 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 @@ -23,6 +23,7 @@ import java.util.Arrays; import javax.cache.Caching; import javax.cache.configuration.MutableConfiguration; +import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.cache.CacheBuilder; import com.hazelcast.cache.HazelcastCachingProvider; import com.hazelcast.config.Config; @@ -40,6 +41,7 @@ import org.springframework.boot.actuate.cache.CacheStatistics; import org.springframework.boot.actuate.cache.CacheStatisticsProvider; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; +import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.cache.ehcache.EhCacheManagerUtils; @@ -60,6 +62,7 @@ import static org.assertj.core.api.Assertions.offset; * Tests for {@link CacheStatisticsAutoConfiguration}. * * @author Stephane Nicoll + * @author Eddú Meléndez */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class CacheStatisticsAutoConfigurationTests { @@ -145,6 +148,14 @@ 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"); @@ -313,4 +324,17 @@ public class CacheStatisticsAutoConfigurationTests { } + @Configuration + static class CaffeineCacheConfig { + + @Bean + public CaffeineCacheManager cacheManager() { + CaffeineCacheManager cacheManager = new CaffeineCacheManager(); + cacheManager.setCaffeine(Caffeine.newBuilder().recordStats()); + cacheManager.setCacheNames(Arrays.asList("books", "speaker")); + return cacheManager; + } + + } + } diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 7b7b4d1410..f2579c5723 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -511,6 +511,11 @@ thymeleaf-layout-dialect true + + com.github.ben-manes.caffeine + caffeine + true + com.github.mxab.thymeleaf.extras thymeleaf-extras-data-attribute diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java index 72f14b7396..5245b0c7d2 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.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. @@ -26,6 +26,7 @@ import org.springframework.util.Assert; * Mappings between {@link CacheType} and {@code @Configuration}. * * @author Phillip Webb + * @author Eddú Meléndez */ final class CacheConfigurations { @@ -42,6 +43,7 @@ final class CacheConfigurations { mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class); + mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class); mappings.put(CacheType.GUAVA, GuavaCacheConfiguration.class); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.java index 8f9213a4cf..eb196e3735 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.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. @@ -44,6 +44,8 @@ public class CacheProperties { */ private List cacheNames = new ArrayList(); + private final Caffeine caffeine = new Caffeine(); + private final EhCache ehcache = new EhCache(); private final Hazelcast hazelcast = new Hazelcast(); @@ -70,6 +72,10 @@ public class CacheProperties { this.cacheNames = cacheNames; } + public Caffeine getCaffeine() { + return this.caffeine; + } + public EhCache getEhcache() { return this.ehcache; } @@ -106,6 +112,27 @@ public class CacheProperties { return null; } + /** + * Caffeine specific cache properties. + */ + public static class Caffeine { + + /** + * The spec to use to create caches. Check CaffeineSpec for more details on + * the spec format. + */ + private String spec; + + public String getSpec() { + return this.spec; + } + + public void setSpec(String spec) { + this.spec = spec; + } + + } + /** * EhCache specific cache properties. */ diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java index f8cfc957e1..5277c1fc47 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.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. @@ -56,6 +56,11 @@ public enum CacheType { */ REDIS, + /** + * Caffeine backed caching. + */ + CAFFEINE, + /** * Guava backed caching. */ 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 new file mode 100644 index 0000000000..dc958fd896 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CaffeineCacheConfiguration.java @@ -0,0 +1,81 @@ +/* + * 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 com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Caffeine; +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.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +/** + * Caffeine cache configuration. + * + * @author Eddú Meléndez + * @since 1.4.0 + */ +@Configuration +@ConditionalOnClass({ Caffeine.class, CaffeineCacheManager.class }) +@ConditionalOnMissingBean(org.springframework.cache.CacheManager.class) +@Conditional({ CacheCondition.class }) +class CaffeineCacheConfiguration { + + @Autowired + private CacheProperties cacheProperties; + + @Autowired(required = false) + private Caffeine caffeine; + + @Autowired(required = false) + private CaffeineSpec caffeineSpec; + + @Autowired(required = false) + private CacheLoader cacheLoader; + + @Bean + @ConditionalOnMissingBean + public CaffeineCacheManager caffeineCacheManager() { + CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager(); + setCacheBuilder(caffeineCacheManager); + if (this.cacheLoader != null) { + caffeineCacheManager.setCacheLoader(this.cacheLoader); + } + caffeineCacheManager.setCacheNames(this.cacheProperties.getCacheNames()); + return caffeineCacheManager; + } + + private void setCacheBuilder(CaffeineCacheManager caffeineCacheManager) { + String specification = this.cacheProperties.getCaffeine().getSpec(); + if (StringUtils.hasText(specification)) { + caffeineCacheManager.setCaffeine(Caffeine.from(specification)); + } + else if (this.caffeineSpec != null) { + caffeineCacheManager.setCaffeine(Caffeine.from(this.caffeineSpec)); + } + else if (this.caffeine != null) { + caffeineCacheManager.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 eb47432bd6..f6bc1b46a8 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 @@ -30,6 +30,8 @@ import javax.cache.configuration.MutableConfiguration; import javax.cache.expiry.CreatedExpiryPolicy; import javax.cache.expiry.Duration; +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.HazelcastInstance; @@ -54,6 +56,8 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.cache.ehcache.EhCacheCacheManager; @@ -580,6 +584,56 @@ 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"); + } + + @Test + public void caffeineCacheNotAllowNullValues() { + load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine", + "spring.cache.caffeine.allow-null-values=false"); + CaffeineCacheManager cacheManager = validateCacheManager( + CaffeineCacheManager.class); + assertThat(cacheManager.isAllowNullValues()).isFalse(); + } + + @Test + public void caffeineCacheWithNullCaches() { + 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"); + validateCaffeineCacheWithStats(); + } + + @Test + public void caffeineCacheExplicitWithCacheBuilder() { + load(CaffeineCacheBuilderConfiguration2.class, "spring.cache.type=caffeine", + "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"); + Cache foo = cacheManager.getCache("foo"); + foo.get("1"); + assertThat(((CaffeineCache) foo).getNativeCache().stats().missCount()).isEqualTo(1L); + } + private void validateGuavaCacheWithStats() { GuavaCacheManager cacheManager = validateCacheManager(GuavaCacheManager.class); assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar"); @@ -837,6 +891,28 @@ public class CacheAutoConfigurationTests { } + @Configuration + @EnableCaching + static class CaffeineCacheBuilderConfiguration { + + @Bean + Caffeine cacheBuilder() { + return Caffeine.newBuilder().maximumSize(10); + } + + } + + @Configuration + @EnableCaching + static class CaffeineCacheBuilderConfiguration2 { + + @Bean + CaffeineSpec caffeineSpec() { + return CaffeineSpec.parse("recordStats"); + } + + } + @Configuration static class CacheManagerCustomizersConfiguration { diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index d615c9ff61..8abd13d27e 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -51,6 +51,7 @@ 2.3.0 3.9.3 2.1.4 + 2.1.0 2.1.9 1.9.2 3.2.2 @@ -624,6 +625,11 @@ + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + com.github.mxab.thymeleaf.extras thymeleaf-extras-data-attribute diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 68dbb3d59d..354aeb9a99 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -63,6 +63,7 @@ content into your application; rather pick only the properties that you need. # SPRING CACHE ({sc-spring-boot-autoconfigure}/cache/CacheProperties.{sc-ext}[CacheProperties]) spring.cache.cache-names= # Comma-separated list of cache names to create if supported by the underlying cache manager. + spring.cache.caffeine.spec= # The spec to use to create caches. Check CaffeineSpec for more details on the spec format. spring.cache.ehcache.config= # The location of the configuration file to use to initialize EhCache. spring.cache.guava.spec= # The spec to use to create caches. Check CacheBuilderSpec for more details on the spec format. spring.cache.hazelcast.config= # The location of the configuration file to use to initialize Hazelcast. From 6741f05af10295048150eeb53fa00355c9036661 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 29 Feb 2016 14:00:43 +0100 Subject: [PATCH 2/2] Polish contribution Closes gh-4903 --- .../CacheStatisticsAutoConfiguration.java | 22 +++---- ...CacheStatisticsAutoConfigurationTests.java | 16 ++--- .../cache/CaffeineCacheConfiguration.java | 36 ++++++---- .../cache/CacheAutoConfigurationTests.java | 65 ++++++++++--------- .../main/asciidoc/spring-boot-features.adoc | 26 ++++++++ .../spring-boot-sample-cache/README.adoc | 7 ++ .../src/main/resources/application.properties | 6 ++ 7 files changed, 118 insertions(+), 60 deletions(-) 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); + CaffeineCacheManager cacheManager = createCacheManager(); + List cacheNames = this.cacheProperties.getCacheNames(); + if (!CollectionUtils.isEmpty(cacheNames)) { + cacheManager.setCacheNames(cacheNames); + } + return this.customizers.customize(cacheManager); + } + + private CaffeineCacheManager createCacheManager() { + CaffeineCacheManager cacheManager = new CaffeineCacheManager(); + setCacheBuilder(cacheManager); if (this.cacheLoader != null) { - caffeineCacheManager.setCacheLoader(this.cacheLoader); + cacheManager.setCacheLoader(this.cacheLoader); } - caffeineCacheManager.setCacheNames(this.cacheProperties.getCacheNames()); - return caffeineCacheManager; + return cacheManager; } - private void setCacheBuilder(CaffeineCacheManager caffeineCacheManager) { + 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,45 +584,52 @@ public class CacheAutoConfigurationTests { validateGuavaCacheWithStats(); } + 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 caffeineCacheWithCaches() { + public void caffeineCacheWithExplicitCaches() { load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine", - "spring.cache.cacheNames[0]=spring", "spring.cache.cacheNames[1]=boot"); + "spring.cache.cacheNames=foo"); CaffeineCacheManager cacheManager = validateCacheManager( CaffeineCacheManager.class); - assertThat(cacheManager.getCacheNames()).containsOnly("spring", "boot"); + 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 caffeineCacheNotAllowNullValues() { - load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine", - "spring.cache.caffeine.allow-null-values=false"); - CaffeineCacheManager cacheManager = validateCacheManager( - CaffeineCacheManager.class); - assertThat(cacheManager.isAllowNullValues()).isFalse(); + public void caffeineCacheWithCustomizers() { + testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "caffeine", + "allCacheManagerCustomizer", "caffeineCacheManagerCustomizer"); } @Test - public void caffeineCacheWithNullCaches() { + 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(); + "spring.cache.cacheNames=foo,bar"); + validateCaffeineCacheWithStats(); } @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"); + load(CaffeineCacheSpecConfiguration.class, "spring.cache.type=caffeine", + "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); validateCaffeineCacheWithStats(); } @Test - public void caffeineCacheExplicitWithCacheBuilder() { - load(CaffeineCacheBuilderConfiguration2.class, "spring.cache.type=caffeine", - "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); + 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(); } @@ -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 #