diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index f50b98392a5..b89c08f682d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -93,7 +93,6 @@ dependencies { optional("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect") optional("org.aspectj:aspectjweaver") optional("org.cache2k:cache2k-spring") - optional("org.cache2k:cache2k-micrometer") optional("org.eclipse.jetty:jetty-webapp") { exclude group: "javax.servlet", module: "javax.servlet-api" } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kDefaults.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kBuilderCustomizer.java similarity index 70% rename from spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kDefaults.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kBuilderCustomizer.java index e43ea68b035..954d1fa5bff 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kDefaults.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kBuilderCustomizer.java @@ -19,14 +19,19 @@ package org.springframework.boot.autoconfigure.cache; import org.cache2k.Cache2kBuilder; /** - * Default configuration for cache2k when Spring boot auto configuration is creating the - * Cache Manager. + * Callback interface that can be implemented by beans wishing to customize the default + * setup for caches added to the manager via addCaches and for dynamically created caches. * * @author Jens Wilke + * @author Stephane Nicoll * @since 2.7.0 */ -public interface Cache2kDefaults { +public interface Cache2kBuilderCustomizer { + /** + * Customize the default cache settings. + * @param builder the builder to customize + */ void customize(Cache2kBuilder builder); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kCacheConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kCacheConfiguration.java index a6b7bd00ece..87dd84d5579 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kCacheConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kCacheConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.cache; import java.util.Collection; +import java.util.function.Function; import org.cache2k.Cache2kBuilder; import org.cache2k.extra.spring.SpringCache2kCacheManager; @@ -34,6 +35,7 @@ import org.springframework.util.CollectionUtils; * Cache2k cache configuration. * * @author Jens Wilke + * @author Stephane Nicoll */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Cache2kBuilder.class, SpringCache2kCacheManager.class }) @@ -43,15 +45,9 @@ class Cache2kCacheConfiguration { @Bean SpringCache2kCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers, - ObjectProvider defaults) { + ObjectProvider cache2kBuilderCustomizers) { SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager(); - Cache2kDefaults specifiedDefaults = defaults.getIfAvailable(); - if (specifiedDefaults != null) { - cacheManager.defaultSetup((builder) -> { - specifiedDefaults.customize(builder); - return builder; - }); - } + cacheManager.defaultSetup(configureDefaults(cache2kBuilderCustomizers)); Collection cacheNames = cacheProperties.getCacheNames(); if (!CollectionUtils.isEmpty(cacheNames)) { cacheManager.setDefaultCacheNames(cacheNames); @@ -59,4 +55,12 @@ class Cache2kCacheConfiguration { return customizers.customize(cacheManager); } + private Function, Cache2kBuilder> configureDefaults( + ObjectProvider cache2kBuilderCustomizers) { + return (builder) -> { + cache2kBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder; + }; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java index 0b0fd8929a6..0dee51e6637 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java index 0499a8cf6cf..4a975ec5d1f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/AbstractCacheAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/AbstractCacheAutoConfigurationTests.java index c1727ccdeb8..01833b5ef9b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/AbstractCacheAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/AbstractCacheAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java index de14de8223a..e23f19c4fdf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.cache; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import javax.cache.Caching; import javax.cache.configuration.CompleteConfiguration; @@ -48,6 +49,7 @@ import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfigurati import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.cache.Cache; +import org.springframework.cache.Cache.ValueWrapper; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; @@ -74,7 +76,6 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; @@ -624,137 +625,60 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests { } @Test - void cache2kCacheWithDynamicCacheCreation() { - this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) - .withPropertyValues("spring.cache.type=cache2k").run((context) -> { - SpringCache2kCacheManager manager = getCacheManager(context, SpringCache2kCacheManager.class); - assertThat(manager.getCacheNames()).isEmpty(); - assertThat(manager.getNativeCacheManager().getName()).isEqualTo("springDefault"); - Cache foo = manager.getCache("dynamic"); - foo.get("1"); - }); - } - - @Test - void cache2kCacheWithCacheNamesInProperties() { + void cache2kCacheWithExplicitCaches() { this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) .withPropertyValues("spring.cache.type=cache2k", "spring.cache.cacheNames=foo,bar").run((context) -> { SpringCache2kCacheManager manager = getCacheManager(context, SpringCache2kCacheManager.class); assertThat(manager.getCacheNames()).containsExactlyInAnyOrder("foo", "bar"); - assertThat(manager.getNativeCacheManager().getName()).isEqualTo("springDefault"); - manager.getCache("foo").get("1"); - manager.getCache("bar").get("2"); - assertThat(manager.getCache("unknown")).isNull(); }); } @Test - void cache2kCacheWithDynamicCacheCreationAndDefaults() { + void cache2kCacheWithCustomizedDefaults() { this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) .withPropertyValues("spring.cache.type=cache2k") - .withBean(Cache2kDefaults.class, () -> (b) -> b.valueType(String.class).loader((key) -> "default")) + .withBean(Cache2kBuilderCustomizer.class, + () -> (builder) -> builder.valueType(String.class).loader((key) -> "default")) .run((context) -> { SpringCache2kCacheManager manager = getCacheManager(context, SpringCache2kCacheManager.class); assertThat(manager.getCacheNames()).isEmpty(); - assertThat(manager.getNativeCacheManager().getName()).isEqualTo("springDefault"); - Cache foo = manager.getCache("fooDynamic"); - assertThat(foo.get("1").get()).isEqualTo("default"); - Cache bar = manager.getCache("barDynamic"); - assertThat(bar.get("1").get()).isEqualTo("default"); - assertThat(manager.getCache("barDynamic")).isSameAs(bar); + Cache dynamic = manager.getCache("dynamic"); + assertThat(dynamic.get("1")).satisfies(hasEntry("default")); + assertThat(dynamic.get("2")).satisfies(hasEntry("default")); }); } @Test - void cache2kCacheWithNamesInPropertiesAndDefaults() { + void cache2kCacheWithCustomizedDefaultsAndExplicitCaches() { this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) .withPropertyValues("spring.cache.type=cache2k", "spring.cache.cacheNames=foo,bar") - .withBean(Cache2kDefaults.class, () -> (b) -> b.valueType(String.class).loader((key) -> "default")) + .withBean(Cache2kBuilderCustomizer.class, + () -> (builder) -> builder.valueType(String.class).loader((key) -> "default")) .run((context) -> { SpringCache2kCacheManager manager = getCacheManager(context, SpringCache2kCacheManager.class); assertThat(manager.getCacheNames()).containsExactlyInAnyOrder("foo", "bar"); - assertThat(manager.getNativeCacheManager().getName()).isEqualTo("springDefault"); - Cache foo = manager.getCache("foo"); - assertThat(foo.get("1").get()).isEqualTo("default"); - Cache bar = manager.getCache("bar"); - assertThat(bar.get("1").get()).isEqualTo("default"); + assertThat(manager.getCache("foo").get("1")).satisfies(hasEntry("default")); + assertThat(manager.getCache("bar").get("1")).satisfies(hasEntry("default")); }); } @Test - void cache2kCacheWithDynamicCacheCreationAndDefaultAndCustomCachesViaCacheManagerCustomizer() { + void cache2kCacheWithCacheManagerCustomizer() { this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) - .withPropertyValues("spring.cache.type=cache2k").withBean(CacheManagerCustomizer.class, - () -> (CacheManagerCustomizer) (cm) -> { - cm.defaultSetup((b) -> b.valueType(String.class).loader((key) -> "default")); - cm.addCache("custom", (b) -> b.valueType(String.class).loader((key) -> "custom")); - }) + .withPropertyValues("spring.cache.type=cache2k") + .withBean(CacheManagerCustomizer.class, + () -> cache2kCacheManagerCustomizer((cacheManager) -> cacheManager.addCache("custom", + (builder) -> builder.valueType(String.class).loader((key) -> "custom")))) .run((context) -> { SpringCache2kCacheManager manager = getCacheManager(context, SpringCache2kCacheManager.class); assertThat(manager.getCacheNames()).containsExactlyInAnyOrder("custom"); - assertThat(manager.getNativeCacheManager().getName()).isEqualTo("springDefault"); - Cache foo = manager.getCache("fooDynamic"); - assertThat(foo.get("1").get()).isEqualTo("default"); - Cache custom = manager.getCache("custom"); - assertThat(custom.get("1").get()).isEqualTo("custom"); + assertThat(manager.getCache("custom").get("1")).satisfies(hasEntry("custom")); }); } - @Test - void cache2kCacheWithNamesInPropertiesAndDefaultsAndCacheManagerCustomizer() { - this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) - .withPropertyValues("spring.cache.type=cache2k", - "spring.cache.cacheNames=foo,bar") - .withBean(Cache2kDefaults.class, () -> (b) -> b.valueType(String.class).loader((key) -> "default")) - .withBean(CacheManagerCustomizer.class, - () -> (CacheManagerCustomizer) (cm) -> cm.addCache("custom", - (b) -> b.valueType(String.class).loader((key) -> "custom"))) - .run((context) -> { - SpringCache2kCacheManager manager = getCacheManager(context, SpringCache2kCacheManager.class); - assertThat(manager.getCacheNames()).containsExactlyInAnyOrder("foo", "bar", "custom"); - assertThat(manager.getNativeCacheManager().getName()).isEqualTo("springDefault"); - Cache foo = manager.getCache("foo"); - assertThat(foo.get("1").get()).isEqualTo("default"); - Cache bar = manager.getCache("bar"); - assertThat(bar.get("1").get()).isEqualTo("default"); - assertThat(manager.isAllowUnknownCache()).isFalse(); - }); - } - - /** - * Default cannot be changed in CacheManagerCustomizer, if cache names are present in - * the properties - */ - @Test - void cache2kCacheNamesAndCacheManagerDefaultsYieldsException() { - this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) - .withPropertyValues("spring.cache.type=cache2k", "spring.cache.cacheNames=foo,bar") - .withBean(CacheManagerCustomizer.class, - () -> (CacheManagerCustomizer) (cm) -> cm - .defaultSetup((b) -> b.entryCapacity(1234))) - .run((context) -> { - assertThatCode(() -> getCacheManager(context, SpringCache2kCacheManager.class)).getRootCause() - .isInstanceOf(IllegalStateException.class); - // close underlying cache manager directly since Spring bean was not - // created - org.cache2k.CacheManager.getInstance(SpringCache2kCacheManager.DEFAULT_SPRING_CACHE_MANAGER_NAME) - .close(); - }); - } - - /** - * Applying defaults via customizer and cache manager is not possible - */ - @Test - void cache2kCacheDefaultsTwiceYieldsException() { - this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) - .withPropertyValues("spring.cache.type=cache2k") - .withBean(Cache2kDefaults.class, () -> (b) -> b.entryCapacity(1234)) - .withBean(CacheManagerCustomizer.class, - () -> (CacheManagerCustomizer) (cm) -> cm - .defaultSetup((b) -> b.entryCapacity(1234))) - .run((context) -> assertThatCode(() -> getCacheManager(context, SpringCache2kCacheManager.class)) - .getRootCause().isInstanceOf(IllegalStateException.class)); + private CacheManagerCustomizer cache2kCacheManagerCustomizer( + Consumer cacheManager) { + return cacheManager::accept; } @Test @@ -818,6 +742,10 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests { }); } + private Consumer hasEntry(Object value) { + return (valueWrapper) -> assertThat(valueWrapper.get()).isEqualTo(value); + } + private void validateCaffeineCacheWithStats(AssertableApplicationContext context) { CaffeineCacheManager manager = getCacheManager(context, CaffeineCacheManager.class); assertThat(manager.getCacheNames()).containsOnly("foo", "bar"); diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index cce1bd847cc..75fd6012942 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -100,6 +100,7 @@ dependencies { } implementation("org.apache.tomcat.embed:tomcat-embed-core") implementation("org.assertj:assertj-core") + implementation("org.cache2k:cache2k-spring") implementation("org.glassfish.jersey.core:jersey-server") implementation("org.glassfish.jersey.containers:jersey-container-servlet-core") implementation("org.hibernate:hibernate-jcache") { diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc index 962db60b80f..b147b5012aa 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/actuator/metrics.adoc @@ -850,6 +850,7 @@ Additional, cache-specific metrics are also available. The following cache libraries are supported: +* Cache2k * Caffeine * EhCache 2 * Hazelcast diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/caching.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/caching.adoc index 60044de8f67..63009d2b28a 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/caching.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/caching.adoc @@ -44,6 +44,7 @@ If you have not defined a bean of type `CacheManager` or a `CacheResolver` named . <> . <> . <> +. <> . <> Additionally, {spring-boot-for-apache-geode}[Spring Boot for Apache Geode] provides {spring-boot-for-apache-geode-docs}#geode-caching-provider[auto-configuration for using Apache Geode as a cache provider]. @@ -230,6 +231,19 @@ The auto-configuration ignores any other generic type. +[[io.caching.provider.cache2k]] +==== Cache2k +https://cache2k.org/[Cache2k] is an in-memory cache. +If the Cache2k spring integration is present, a `SpringCache2kCacheManager` is auto-configured. + +Caches can be created on startup by setting the configprop:spring.cache.cache-names[] property. +Cache defaults can be customized using a `Cache2kBuilderCustomizer` bean. +The following example shows a customizer that configures the capacity of the cache to 200 entries, with an expiration of 5 minutes: + +include::code:MyCache2kDefaultsConfiguration[] + + + [[io.caching.provider.simple]] ==== Simple If none of the other providers can be found, a simple implementation using a `ConcurrentHashMap` as the cache store is configured. diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/caching/provider/cache2k/MyCache2kDefaultsConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/caching/provider/cache2k/MyCache2kDefaultsConfiguration.java new file mode 100644 index 00000000000..8e18d6071d7 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/caching/provider/cache2k/MyCache2kDefaultsConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2022 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 + * + * https://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.docs.io.caching.provider.cache2k; + +import java.util.concurrent.TimeUnit; + +import org.springframework.boot.autoconfigure.cache.Cache2kBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class MyCache2kDefaultsConfiguration { + + @Bean + public Cache2kBuilderCustomizer myCache2kDefaultsCustomizer() { + // @formatter:off + return (builder) -> builder.entryCapacity(200) + .expireAfterWrite(5, TimeUnit.MINUTES); + // @formatter:on + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/caching/provider/cache2k/MyCache2kDefaultsConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/caching/provider/cache2k/MyCache2kDefaultsConfiguration.kt new file mode 100644 index 00000000000..b359d6e5d78 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/caching/provider/cache2k/MyCache2kDefaultsConfiguration.kt @@ -0,0 +1,18 @@ +package org.springframework.boot.docs.io.caching.provider.cache2k + +import org.springframework.boot.autoconfigure.cache.Cache2kBuilderCustomizer +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.util.concurrent.TimeUnit + +@Configuration(proxyBeanMethods = false) +class MyCache2kDefaultsConfiguration { + + @Bean + fun myCache2kDefaultsCustomizer(): Cache2kBuilderCustomizer { + return Cache2kBuilderCustomizer { builder -> + builder.entryCapacity(200) + .expireAfterWrite(5, TimeUnit.MINUTES) + } + } +} \ No newline at end of file