Move cache metrics auto-configuration into spring-boot-cache

This commit is contained in:
Andy Wilkinson
2025-05-20 09:18:03 +01:00
committed by Phillip Webb
parent ff0876c001
commit 84d01cbad4
10 changed files with 24 additions and 31 deletions

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2012-2025 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.cache.actuate.metrics.autoconfigure;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.spring.cache.HazelcastCache;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.cache2k.Cache2kBuilder;
import org.cache2k.extra.micrometer.Cache2kCacheMetrics;
import org.cache2k.extra.spring.SpringCache2kCache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.cache.actuate.metrics.Cache2kCacheMeterBinderProvider;
import org.springframework.boot.cache.actuate.metrics.CacheMeterBinderProvider;
import org.springframework.boot.cache.actuate.metrics.CaffeineCacheMeterBinderProvider;
import org.springframework.boot.cache.actuate.metrics.HazelcastCacheMeterBinderProvider;
import org.springframework.boot.cache.actuate.metrics.JCacheCacheMeterBinderProvider;
import org.springframework.boot.cache.actuate.metrics.RedisCacheMeterBinderProvider;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.jcache.JCacheCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCache;
/**
* Configure {@link CacheMeterBinderProvider} beans.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ MeterBinder.class, CacheMeterBinderProvider.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 {
@Bean
CaffeineCacheMeterBinderProvider caffeineCacheMeterBinderProvider() {
return new CaffeineCacheMeterBinderProvider();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HazelcastCache.class, Hazelcast.class })
static class HazelcastCacheMeterBinderProviderConfiguration {
@Bean
HazelcastCacheMeterBinderProvider hazelcastCacheMeterBinderProvider() {
return new HazelcastCacheMeterBinderProvider();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JCacheCache.class, javax.cache.CacheManager.class })
static class JCacheCacheMeterBinderProviderConfiguration {
@Bean
JCacheCacheMeterBinderProvider jCacheCacheMeterBinderProvider() {
return new JCacheCacheMeterBinderProvider();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisCache.class)
static class RedisCacheMeterBinderProviderConfiguration {
@Bean
RedisCacheMeterBinderProvider redisCacheMeterBinderProvider() {
return new RedisCacheMeterBinderProvider();
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2012-2025 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.cache.actuate.metrics.autoconfigure;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Import;
/**
* {@link EnableAutoConfiguration Auto-configuration} for metrics on all available
* {@link Cache caches}.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
@AutoConfiguration(after = { CacheAutoConfiguration.class },
afterName = "org.springframework.boot.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration")
@ConditionalOnBean(CacheManager.class)
@ConditionalOnClass(MeterRegistry.class)
@Import({ CacheMeterBinderProvidersConfiguration.class, CacheMetricsRegistrarConfiguration.class })
public class CacheMetricsAutoConfiguration {
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2012-2025 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.cache.actuate.metrics.autoconfigure;
import java.util.Collection;
import java.util.Map;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.cache.actuate.metrics.CacheMeterBinderProvider;
import org.springframework.boot.cache.actuate.metrics.CacheMetricsRegistrar;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
/**
* Configure a {@link CacheMetricsRegistrar} and register all available {@link Cache
* caches}.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean({ CacheMeterBinderProvider.class, MeterRegistry.class })
class CacheMetricsRegistrarConfiguration {
private static final String CACHE_MANAGER_SUFFIX = "cacheManager";
private final MeterRegistry registry;
private final CacheMetricsRegistrar cacheMetricsRegistrar;
private final Map<String, CacheManager> cacheManagers;
CacheMetricsRegistrarConfiguration(MeterRegistry registry, Collection<CacheMeterBinderProvider<?>> binderProviders,
ConfigurableListableBeanFactory beanFactory) {
this.registry = registry;
this.cacheManagers = SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, CacheManager.class);
this.cacheMetricsRegistrar = new CacheMetricsRegistrar(this.registry, binderProviders);
bindCachesToRegistry();
}
@Bean
CacheMetricsRegistrar cacheMetricsRegistrar() {
return this.cacheMetricsRegistrar;
}
private void bindCachesToRegistry() {
this.cacheManagers.forEach(this::bindCacheManagerToRegistry);
}
private void bindCacheManagerToRegistry(String beanName, CacheManager cacheManager) {
cacheManager.getCacheNames()
.forEach((cacheName) -> bindCacheToRegistry(beanName, cacheManager.getCache(cacheName)));
}
private void bindCacheToRegistry(String beanName, Cache cache) {
Tag cacheManagerTag = Tag.of("cache.manager", getCacheManagerName(beanName));
this.cacheMetricsRegistrar.bindCacheToRegistry(cache, cacheManagerTag);
}
/**
* Get the name of a {@link CacheManager} based on its {@code beanName}.
* @param beanName the name of the {@link CacheManager} bean
* @return a name for the given cache manager
*/
private String getCacheManagerName(String beanName) {
if (beanName.length() > CACHE_MANAGER_SUFFIX.length()
&& StringUtils.endsWithIgnoreCase(beanName, CACHE_MANAGER_SUFFIX)) {
return beanName.substring(0, beanName.length() - CACHE_MANAGER_SUFFIX.length());
}
return beanName;
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for cache metrics.
*/
package org.springframework.boot.cache.actuate.metrics.autoconfigure;

View File

@@ -1,2 +1,3 @@
org.springframework.boot.cache.actuate.endpoint.autoconfigure.CachesEndpointAutoConfiguration
org.springframework.boot.cache.actuate.metrics.autoconfigure.CacheMetricsAutoConfiguration
org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2012-2025 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.autoconfigure.metrics.cache;
import java.util.Collections;
import java.util.List;
import com.github.benmanes.caffeine.cache.CaffeineSpec;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.cache.actuate.metrics.autoconfigure.CacheMetricsAutoConfiguration;
import org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CacheMetricsAutoConfiguration}.
*
* @author Stephane Nicoll
*/
class CacheMetricsAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withBean(SimpleMeterRegistry.class)
.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("cache.manager", "cacheManager").meter();
registry.get("cache.gets").tags("name", "cache2").tags("cache.manager", "cacheManager").meter();
});
}
@Test
void autoConfiguredCacheManagerIsInstrumented() {
this.contextRunner
.withPropertyValues("spring.cache.type=caffeine", "spring.cache.cache-names=cache1,cache2",
"spring.cache.caffeine.spec=recordStats")
.run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
registry.get("cache.gets").tags("name", "cache1").tags("cache.manager", "cacheManager").meter();
registry.get("cache.gets").tags("name", "cache2").tags("cache.manager", "cacheManager").meter();
});
}
@Test
void autoConfiguredNonSupportedCacheManagerIsIgnored() {
this.contextRunner.withPropertyValues("spring.cache.type=simple", "spring.cache.cache-names=cache1,cache2")
.run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("cache.gets")
.tags("name", "cache1")
.tags("cache.manager", "cacheManager")
.meter()).isNull();
assertThat(registry.find("cache.gets")
.tags("name", "cache2")
.tags("cache.manager", "cacheManager")
.meter()).isNull();
});
}
@Test
void cacheInstrumentationCanBeDisabled() {
this.contextRunner
.withPropertyValues("management.metrics.enable.cache=false", "spring.cache.type=caffeine",
"spring.cache.cache-names=cache1")
.run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("cache.requests")
.tags("name", "cache1")
.tags("cache.manager", "cacheManager")
.meter()).isNull();
});
}
@Test
void customCacheManagersAreInstrumented() {
this.contextRunner
.withPropertyValues("spring.cache.type=caffeine", "spring.cache.cache-names=cache1,cache2",
"spring.cache.caffeine.spec=recordStats")
.withUserConfiguration(CustomCacheManagersConfiguration.class)
.run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("cache.gets").meters()).map((meter) -> meter.getId().getTag("cache"))
.containsOnly("standard", "non-default");
});
}
@Configuration(proxyBeanMethods = false)
static class CustomCacheManagersConfiguration implements CachingConfigurer {
@Bean
CacheManager standardCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeineSpec(CaffeineSpec.parse("recordStats"));
cacheManager.setCacheNames(List.of("standard"));
return cacheManager;
}
@Bean(defaultCandidate = false)
CacheManager nonDefaultCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeineSpec(CaffeineSpec.parse("recordStats"));
cacheManager.setCacheNames(List.of("non-default"));
return cacheManager;
}
@Bean(autowireCandidate = false)
CacheManager nonAutowireCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeineSpec(CaffeineSpec.parse("recordStats"));
cacheManager.setCacheNames(List.of("non-autowire"));
return cacheManager;
}
@Bean
@Override
public CacheResolver cacheResolver() {
return (context) -> Collections.emptyList();
}
}
@Configuration(proxyBeanMethods = false)
@EnableCaching
static class CachingConfiguration {
}
}