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 b858077f15d..2a68a7c7c62 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 @@ -30,6 +30,7 @@ import org.springframework.boot.actuate.metrics.cache.HazelcastCacheMeterBinderP import org.springframework.boot.actuate.metrics.cache.JCacheCacheMeterBinderProvider; import org.springframework.boot.actuate.metrics.cache.RedisCacheMeterBinderProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cache.caffeine.CaffeineCache; import org.springframework.cache.jcache.JCacheCache; import org.springframework.context.annotation.Bean; @@ -80,6 +81,7 @@ class CacheMeterBinderProvidersConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ JCacheCache.class, javax.cache.CacheManager.class }) + @ConditionalOnMissingBean(JCacheCacheMeterBinderProvider.class) static class JCacheCacheMeterBinderProviderConfiguration { @Bean 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 89b97b541d7..9278327bef6 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 @@ -24,6 +24,7 @@ import io.micrometer.core.instrument.MeterRegistry; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; +import org.springframework.boot.actuate.metrics.cache.JCacheCacheMeterBinderProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -113,6 +114,23 @@ class CacheMetricsAutoConfigurationTests { }); } + @Test + void jcacheCacheMeterBinderProviderIsAutoConfigured() { + this.contextRunner.withPropertyValues("spring.cache.type=simple", "spring.cache.cache-names=cache1,cache2") + .run((context) -> assertThat(context).hasSingleBean(JCacheCacheMeterBinderProvider.class) + .hasBean("jCacheCacheMeterBinderProvider")); + } + + @Test + void jcacheCacheMeterBinderProviderBacksOff() { + this.contextRunner.withPropertyValues("spring.cache.type=simple", "spring.cache.cache-names=cache1,cache2") + .withBean("customProvider", JCacheCacheMeterBinderProvider.class, + () -> new JCacheCacheMeterBinderProvider(true)) + .run((context) -> assertThat(context).hasSingleBean(JCacheCacheMeterBinderProvider.class) + .hasBean("customProvider") + .doesNotHaveBean("jCacheCacheMeterBinderProvider")); + } + @Configuration(proxyBeanMethods = false) static class CustomCacheManagersConfiguration implements CachingConfigurer { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/JCacheCacheMeterBinderProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/JCacheCacheMeterBinderProvider.java index 5d4ae4d270d..66a7dbce10b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/JCacheCacheMeterBinderProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/JCacheCacheMeterBinderProvider.java @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.metrics.cache; +import io.micrometer.core.instrument.FunctionCounter; +import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.binder.cache.JCacheMetrics; @@ -30,9 +32,33 @@ import org.springframework.cache.jcache.JCacheCache; */ public class JCacheCacheMeterBinderProvider implements CacheMeterBinderProvider { + private final boolean registerCacheRemovalsAsFunctionCounter; + + /** + * Creates a {@code JCacheCacheMeterBinderProvider} that registers cache removals as a + * {@link Gauge}. + */ + public JCacheCacheMeterBinderProvider() { + this(false); + } + + /** + * Creates a {@code JCacheCacheMeterBinderProvider} that registers cache removals with + * a meter type that depends on the value of + * {@code registerCacheRemovalsAsFunctionCounter}. When {@code false}, cache removals + * are registered as a {@link Gauge}. When {@code true}, cache removals are registered + * as a {@link FunctionCounter}. + * @param registerCacheRemovalsAsFunctionCounter whether to register removals as a + * gauge or a function counter + * @since 3.4.12 + */ + public JCacheCacheMeterBinderProvider(boolean registerCacheRemovalsAsFunctionCounter) { + this.registerCacheRemovalsAsFunctionCounter = registerCacheRemovalsAsFunctionCounter; + } + @Override public MeterBinder getMeterBinder(JCacheCache cache, Iterable tags) { - return new JCacheMetrics<>(cache.getNativeCache(), tags); + return new JCacheMetrics<>(cache.getNativeCache(), tags, this.registerCacheRemovalsAsFunctionCounter); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/JCacheCacheMeterBinderProviderTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/JCacheCacheMeterBinderProviderTests.java index cfa67134f62..fa9c9a2ae90 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/JCacheCacheMeterBinderProviderTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/JCacheCacheMeterBinderProviderTests.java @@ -53,6 +53,20 @@ class JCacheCacheMeterBinderProviderTests { JCacheCache cache = new JCacheCache(this.nativeCache); MeterBinder meterBinder = new JCacheCacheMeterBinderProvider().getMeterBinder(cache, Collections.emptyList()); assertThat(meterBinder).isInstanceOf(JCacheMetrics.class); + assertThat(meterBinder).extracting("registerCacheRemovalsAsFunctionCounter").isEqualTo(false); + } + + @Test + void jCacheCacheProviderRegisteringRemovalsAsAFunctionCounter() throws URISyntaxException { + javax.cache.CacheManager cacheManager = mock(javax.cache.CacheManager.class); + given(cacheManager.getURI()).willReturn(new URI("/test")); + given(this.nativeCache.getCacheManager()).willReturn(cacheManager); + given(this.nativeCache.getName()).willReturn("test"); + JCacheCache cache = new JCacheCache(this.nativeCache); + MeterBinder meterBinder = new JCacheCacheMeterBinderProvider(true).getMeterBinder(cache, + Collections.emptyList()); + assertThat(meterBinder).isInstanceOf(JCacheMetrics.class); + assertThat(meterBinder).extracting("registerCacheRemovalsAsFunctionCounter").isEqualTo(true); } }