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..b89c08f682 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -92,6 +92,7 @@ dependencies { optional("com.zaxxer:HikariCP") optional("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect") optional("org.aspectj:aspectjweaver") + optional("org.cache2k:cache2k-spring") 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/Cache2kBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kBuilderCustomizer.java new file mode 100644 index 0000000000..954d1fa5bf --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kBuilderCustomizer.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.autoconfigure.cache; + +import org.cache2k.Cache2kBuilder; + +/** + * 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 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 new file mode 100644 index 0000000000..87dd84d557 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/Cache2kCacheConfiguration.java @@ -0,0 +1,66 @@ +/* + * 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 java.util.function.Function; + +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 + * @author Stephane Nicoll + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ Cache2kBuilder.class, SpringCache2kCacheManager.class }) +@ConditionalOnMissingBean(CacheManager.class) +@Conditional(CacheCondition.class) +class Cache2kCacheConfiguration { + + @Bean + SpringCache2kCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers, + ObjectProvider cache2kBuilderCustomizers) { + SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager(); + cacheManager.defaultSetup(configureDefaults(cache2kBuilderCustomizers)); + Collection cacheNames = cacheProperties.getCacheNames(); + if (!CollectionUtils.isEmpty(cacheNames)) { + cacheManager.setDefaultCacheNames(cacheNames); + } + 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 9fe6aacb0f..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. @@ -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..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. @@ -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..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. @@ -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..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; @@ -33,6 +34,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; @@ -47,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; @@ -621,6 +624,70 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests { } } + @Test + 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"); + }); + } + + @Test + void cache2kCacheWithCustomizedDefaults() { + this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) + .withPropertyValues("spring.cache.type=cache2k") + .withBean(Cache2kBuilderCustomizer.class, + () -> (builder) -> builder.valueType(String.class).loader((key) -> "default")) + .run((context) -> { + SpringCache2kCacheManager manager = getCacheManager(context, SpringCache2kCacheManager.class); + assertThat(manager.getCacheNames()).isEmpty(); + Cache dynamic = manager.getCache("dynamic"); + assertThat(dynamic.get("1")).satisfies(hasEntry("default")); + assertThat(dynamic.get("2")).satisfies(hasEntry("default")); + }); + } + + @Test + void cache2kCacheWithCustomizedDefaultsAndExplicitCaches() { + this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) + .withPropertyValues("spring.cache.type=cache2k", "spring.cache.cacheNames=foo,bar") + .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.getCache("foo").get("1")).satisfies(hasEntry("default")); + assertThat(manager.getCache("bar").get("1")).satisfies(hasEntry("default")); + }); + } + + @Test + void cache2kCacheWithCacheManagerCustomizer() { + this.contextRunner.withUserConfiguration(DefaultCacheConfiguration.class) + .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.getCache("custom").get("1")).satisfies(hasEntry("custom")); + }); + } + + private CacheManagerCustomizer cache2kCacheManagerCustomizer( + Consumer cacheManager) { + return cacheManager::accept; + } + + @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) @@ -675,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-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" 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