From 774f61fcb53b212936c663d3fe94767b594a5367 Mon Sep 17 00:00:00 2001 From: Jens Wilke Date: Fri, 11 Feb 2022 00:37:23 +0100 Subject: [PATCH 1/2] Add support for cache2k in memory caching See gh-28498 --- .../build.gradle | 4 + ...acheMeterBinderProvidersConfiguration.java | 17 ++- .../CacheMetricsAutoConfigurationTests.java | 12 +- .../spring-boot-actuator/build.gradle | 2 + .../Cache2kCacheMeterBinderProvider.java | 37 +++++ .../Cache2kCacheMeterBinderProviderTests.java | 44 ++++++ .../spring-boot-autoconfigure/build.gradle | 2 + .../cache/Cache2kCacheConfiguration.java | 62 ++++++++ .../autoconfigure/cache/Cache2kDefaults.java | 32 ++++ .../cache/CacheConfigurations.java | 1 + .../boot/autoconfigure/cache/CacheType.java | 5 + .../AbstractCacheAutoConfigurationTests.java | 8 + .../cache/CacheAutoConfigurationTests.java | 143 ++++++++++++++++++ .../spring-boot-dependencies/build.gradle | 12 ++ 14 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/Cache2kCacheMeterBinderProvider.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/Cache2kCacheMeterBinderProviderTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kCacheConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kDefaults.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index a9dacf5142..395a71c8a5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -98,6 +98,8 @@ dependencies { optional("org.apache.tomcat.embed:tomcat-embed-el") optional("org.apache.tomcat:tomcat-jdbc") optional("org.aspectj:aspectjweaver") + optional("org.cache2k:cache2k-micrometer") + optional("org.cache2k:cache2k-spring") optional("org.eclipse.jetty:jetty-server") { exclude group: "javax.servlet", module: "javax.servlet-api" } @@ -171,6 +173,7 @@ dependencies { testImplementation("org.aspectj:aspectjrt") testImplementation("org.assertj:assertj-core") testImplementation("org.awaitility:awaitility") + testImplementation("org.cache2k:cache2k-api") testImplementation("org.eclipse.jetty:jetty-webapp") { exclude group: "javax.servlet", module: "javax.servlet-api" } @@ -194,6 +197,7 @@ dependencies { testRuntimeOnly("jakarta.management.j2ee:jakarta.management.j2ee-api") testRuntimeOnly("jakarta.transaction:jakarta.transaction-api") + testRuntimeOnly("org.cache2k:cache2k-core") testRuntimeOnly("org.springframework.security:spring-security-oauth2-jose") testRuntimeOnly("org.springframework.security:spring-security-oauth2-resource-server") testRuntimeOnly("org.springframework.security:spring-security-saml2-service-provider") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMeterBinderProvidersConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMeterBinderProvidersConfiguration.java index e756fc201c..48c9e09cc5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMeterBinderProvidersConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMeterBinderProvidersConfiguration.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. @@ -20,7 +20,11 @@ import com.hazelcast.core.Hazelcast; import com.hazelcast.spring.cache.HazelcastCache; import io.micrometer.core.instrument.binder.MeterBinder; import net.sf.ehcache.Ehcache; +import org.cache2k.Cache2kBuilder; +import org.cache2k.extra.micrometer.Cache2kCacheMetrics; +import org.cache2k.extra.spring.SpringCache2kCache; +import org.springframework.boot.actuate.metrics.cache.Cache2kCacheMeterBinderProvider; import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProvider; import org.springframework.boot.actuate.metrics.cache.CaffeineCacheMeterBinderProvider; import org.springframework.boot.actuate.metrics.cache.EhCache2CacheMeterBinderProvider; @@ -44,6 +48,17 @@ import org.springframework.data.redis.cache.RedisCache; @ConditionalOnClass(MeterBinder.class) class CacheMeterBinderProvidersConfiguration { + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ Cache2kBuilder.class, SpringCache2kCache.class, Cache2kCacheMetrics.class }) + static class Cache2kCacheMeterBinderProviderConfiguration { + + @Bean + Cache2kCacheMeterBinderProvider cache2kCacheMeterBinderProvider() { + return new Cache2kCacheMeterBinderProvider(); + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ CaffeineCache.class, com.github.benmanes.caffeine.cache.Cache.class }) static class CaffeineCacheMeterBinderProviderConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsAutoConfigurationTests.java index 8850c29799..556b531289 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsAutoConfigurationTests.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. @@ -39,6 +39,16 @@ class CacheMetricsAutoConfigurationTests { .withUserConfiguration(CachingConfiguration.class).withConfiguration( AutoConfigurations.of(CacheAutoConfiguration.class, CacheMetricsAutoConfiguration.class)); + @Test + void autoConfiguredCache2kIsInstrumented() { + this.contextRunner.withPropertyValues("spring.cache.type=cache2k", "spring.cache.cache-names=cache1,cache2") + .run((context) -> { + MeterRegistry registry = context.getBean(MeterRegistry.class); + registry.get("cache.gets").tags("name", "cache1").tags("cacheManager", "cacheManager").meter(); + registry.get("cache.gets").tags("name", "cache2").tags("cacheManager", "cacheManager").meter(); + }); + } + @Test void autoConfiguredCacheManagerIsInstrumented() { this.contextRunner.withPropertyValues("spring.cache.type=caffeine", "spring.cache.cache-names=cache1,cache2") diff --git a/spring-boot-project/spring-boot-actuator/build.gradle b/spring-boot-project/spring-boot-actuator/build.gradle index b51d56480a..e0d844ed1b 100644 --- a/spring-boot-project/spring-boot-actuator/build.gradle +++ b/spring-boot-project/spring-boot-actuator/build.gradle @@ -46,6 +46,8 @@ dependencies { } optional("org.apache.tomcat.embed:tomcat-embed-core") optional("org.aspectj:aspectjweaver") + optional("org.cache2k:cache2k-micrometer") + optional("org.cache2k:cache2k-spring") optional("org.eclipse.jetty:jetty-server") { exclude(group: "javax.servlet", module: "javax.servlet-api") } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/Cache2kCacheMeterBinderProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/Cache2kCacheMeterBinderProvider.java new file mode 100644 index 0000000000..d2954b4b46 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/Cache2kCacheMeterBinderProvider.java @@ -0,0 +1,37 @@ +/* + * 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.actuate.metrics.cache; + +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.MeterBinder; +import org.cache2k.extra.micrometer.Cache2kCacheMetrics; +import org.cache2k.extra.spring.SpringCache2kCache; + +/** + * {@link CacheMeterBinderProvider} implementation for cache2k. + * + * @author Jens Wilke + * @since 2.7.0 + */ +public class Cache2kCacheMeterBinderProvider implements CacheMeterBinderProvider { + + @Override + public MeterBinder getMeterBinder(SpringCache2kCache cache, Iterable tags) { + return new Cache2kCacheMetrics(cache.getNativeCache(), tags); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/Cache2kCacheMeterBinderProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/Cache2kCacheMeterBinderProviderTests.java new file mode 100644 index 0000000000..38bccbb10d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/Cache2kCacheMeterBinderProviderTests.java @@ -0,0 +1,44 @@ +/* + * 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.actuate.metrics.cache; + +import java.util.Collections; + +import io.micrometer.core.instrument.binder.MeterBinder; +import org.cache2k.extra.micrometer.Cache2kCacheMetrics; +import org.cache2k.extra.spring.SpringCache2kCacheManager; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Cache2kCacheMeterBinderProvider}. + * + * @author Stephane Nicoll + */ +class Cache2kCacheMeterBinderProviderTests { + + @Test + void cache2kCacheProvider() { + SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager() + .addCaches((builder) -> builder.name("test")); + MeterBinder meterBinder = new Cache2kCacheMeterBinderProvider().getMeterBinder(cacheManager.getCache("test"), + Collections.emptyList()); + assertThat(meterBinder).isInstanceOf(Cache2kCacheMetrics.class); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 28b0ced1b9..f50b98392a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -92,6 +92,8 @@ dependencies { optional("com.zaxxer:HikariCP") 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/Cache2kCacheConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kCacheConfiguration.java new file mode 100644 index 0000000000..a6b7bd00ec --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kCacheConfiguration.java @@ -0,0 +1,62 @@ +/* + * 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.autoconfigure.cache; + +import java.util.Collection; + +import org.cache2k.Cache2kBuilder; +import org.cache2k.extra.spring.SpringCache2kCacheManager; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; + +/** + * Cache2k cache configuration. + * + * @author Jens Wilke + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ Cache2kBuilder.class, SpringCache2kCacheManager.class }) +@ConditionalOnMissingBean(CacheManager.class) +@Conditional(CacheCondition.class) +class Cache2kCacheConfiguration { + + @Bean + SpringCache2kCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers, + ObjectProvider defaults) { + SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager(); + Cache2kDefaults specifiedDefaults = defaults.getIfAvailable(); + if (specifiedDefaults != null) { + cacheManager.defaultSetup((builder) -> { + specifiedDefaults.customize(builder); + return builder; + }); + } + Collection cacheNames = cacheProperties.getCacheNames(); + if (!CollectionUtils.isEmpty(cacheNames)) { + cacheManager.setDefaultCacheNames(cacheNames); + } + return customizers.customize(cacheManager); + } + +} 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/Cache2kDefaults.java new file mode 100644 index 0000000000..e43ea68b03 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kDefaults.java @@ -0,0 +1,32 @@ +/* + * 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.autoconfigure.cache; + +import org.cache2k.Cache2kBuilder; + +/** + * Default configuration for cache2k when Spring boot auto configuration is creating the + * Cache Manager. + * + * @author Jens Wilke + * @since 2.7.0 + */ +public interface Cache2kDefaults { + + void customize(Cache2kBuilder 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 9fe6aacb0f..0b0fd8929a 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 @@ -43,6 +43,7 @@ final class CacheConfigurations { mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName()); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName()); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName()); + mappings.put(CacheType.CACHE2K, Cache2kCacheConfiguration.class.getName()); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName()); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName()); MAPPINGS = Collections.unmodifiableMap(mappings); 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 e63eea28e3..0499a8cf6c 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 @@ -61,6 +61,11 @@ public enum CacheType { */ REDIS, + /** + * Cache2k backed caching. + */ + CACHE2K, + /** * Caffeine backed caching. */ 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 b6f85a5718..c1727ccdeb 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 @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import com.hazelcast.spring.cache.HazelcastCacheManager; +import org.cache2k.extra.spring.SpringCache2kCacheManager; import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -135,6 +136,13 @@ abstract class AbstractCacheAutoConfigurationTests { }; } + @Bean + CacheManagerCustomizer cache2kCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { + + }; + } + @Bean CacheManagerCustomizer caffeineCacheManagerCustomizer() { return new CacheManagerTestCustomizer() { 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 a7740adcb1..de14de8223 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 @@ -33,6 +33,7 @@ import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.spring.cache.HazelcastCacheManager; import net.sf.ehcache.Status; +import org.cache2k.extra.spring.SpringCache2kCacheManager; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.jcache.embedded.JCachingProvider; import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager; @@ -73,6 +74,7 @@ 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; @@ -621,6 +623,147 @@ 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() { + 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() { + this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) + .withPropertyValues("spring.cache.type=cache2k") + .withBean(Cache2kDefaults.class, () -> (b) -> b.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); + }); + } + + @Test + void cache2kCacheWithNamesInPropertiesAndDefaults() { + 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")) + .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"); + }); + } + + @Test + void cache2kCacheWithDynamicCacheCreationAndDefaultAndCustomCachesViaCacheManagerCustomizer() { + 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")); + }) + .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"); + }); + } + + @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)); + } + + @Test + void cache2kCacheWithCustomizers() { + this.contextRunner.withUserConfiguration(DefaultCacheAndCustomizersConfiguration.class) + .withPropertyValues("spring.cache.type=cache2k") + .run(verifyCustomizers("allCacheManagerCustomizer", "cache2kCacheManagerCustomizer")); + } + @Test void caffeineCacheWithExplicitCaches() { this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 4a3822b591..a2737d3a38 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -158,6 +158,18 @@ bom { ] } } + library("cache2k", "2.6.1.Final") { + group("org.cache2k") { + modules = [ + "cache2k-api", + "cache2k-config", + "cache2k-core", + "cache2k-jcache", + "cache2k-micrometer", + "cache2k-spring" + ] + } + } library("Caffeine", "2.9.3") { prohibit("[3.0.0,)") { because "it requires Java 11" From a2959bbcf2b60cf2f862f27e8fbfe81802956626 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 21 Mar 2022 09:28:44 +0100 Subject: [PATCH 2/2] Polish "Add support for cache2k in memory caching" See gh-28498 --- .../spring-boot-autoconfigure/build.gradle | 1 - ...lts.java => Cache2kBuilderCustomizer.java} | 11 +- .../cache/Cache2kCacheConfiguration.java | 20 +-- .../cache/CacheConfigurations.java | 2 +- .../boot/autoconfigure/cache/CacheType.java | 2 +- .../AbstractCacheAutoConfigurationTests.java | 2 +- .../cache/CacheAutoConfigurationTests.java | 126 ++++-------------- .../spring-boot-docs/build.gradle | 1 + .../src/docs/asciidoc/actuator/metrics.adoc | 1 + .../src/docs/asciidoc/io/caching.adoc | 14 ++ .../MyCache2kDefaultsConfiguration.java | 36 +++++ .../cache2k/MyCache2kDefaultsConfiguration.kt | 18 +++ 12 files changed, 120 insertions(+), 114 deletions(-) rename spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/{Cache2kDefaults.java => Cache2kBuilderCustomizer.java} (70%) create mode 100644 spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/caching/provider/cache2k/MyCache2kDefaultsConfiguration.java create mode 100644 spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/caching/provider/cache2k/MyCache2kDefaultsConfiguration.kt diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index f50b98392a..b89c08f682 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 e43ea68b03..954d1fa5bf 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 a6b7bd00ec..87dd84d557 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 0b0fd8929a..0dee51e663 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 0499a8cf6c..4a975ec5d1 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 c1727ccdeb..01833b5ef9 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 de14de8223..e23f19c4fd 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 cce1bd847c..75fd601294 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 962db60b80..b147b5012a 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 60044de8f6..63009d2b28 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 0000000000..8e18d6071d --- /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 0000000000..b359d6e5d7 --- /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