Add cache actuator endpoint

This commits adds an actuator endpoint which lists the caches per
context and cacheManager and provides a delete operation to clear the
caches. As the statistics are exposed via the metrics endpoint they are
not included

See gh-12216
This commit is contained in:
Johannes Edmeier
2018-02-25 19:34:42 +01:00
committed by Stephane Nicoll
parent 0699f65969
commit 1a57673345
7 changed files with 394 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2018 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
*
* http://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.cache;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.cache.CachesEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link CachesEndpoint}.
*
* @author Johannes Edmeier
* @since 2.0.0
*/
@Configuration
@ConditionalOnClass(CacheManager.class)
@AutoConfigureAfter(CacheAutoConfiguration.class)
public class CachesEndpointAutoConfiguration {
@Bean
@ConditionalOnBean(CacheManager.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public CachesEndpoint cachesEndpoint(ApplicationContext context) {
return new CachesEndpoint(context);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2017 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
*
* http://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 actuator cache concerns.
*/
package org.springframework.boot.actuate.autoconfigure.cache;

View File

@@ -3,6 +3,7 @@ org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthIndicatorAutoCon
org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthIndicatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.ReactiveCloudFoundryActuatorAutoConfiguration,\

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2012-2017 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
*
* http://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.cache;
import org.junit.Test;
import org.springframework.boot.actuate.cache.CachesEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link CachesEndpointAutoConfiguration}.
*
* @author Johannes Edmeier
*/
public class CachesEndpointAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(CachesEndpointAutoConfiguration.class))
.withUserConfiguration(CacheConfiguration.class);
@Test
public void runShouldHaveEndpointBean() {
this.contextRunner
.run((context) -> assertThat(context).hasSingleBean(CachesEndpoint.class));
}
@Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoint.caches.enabled:false")
.run((context) -> assertThat(context)
.doesNotHaveBean(CachesEndpoint.class));
}
@Configuration
static class CacheConfiguration {
@Bean
public CacheManager cacheManager() {
return mock(CacheManager.class);
}
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2012-2017 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
*
* http://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.cache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.Nullable;
import static java.util.stream.Collectors.toList;
/**
* {@link Endpoint} to expose cache operations.
*
* @author Johannes Edmeuer
* @since 2.0.2
*/
@Endpoint(id = "caches")
public class CachesEndpoint {
private final ApplicationContext context;
public CachesEndpoint(ApplicationContext context) {
this.context = context;
}
@ReadOperation
public CachesDescriptor caches() {
List<CacheDescriptor> caches = new ArrayList<>();
this.context.getBeansOfType(CacheManager.class)
.forEach((name, cacheManager) -> caches.addAll(
getCacheDescriptors(name, cacheManager.getCacheNames())));
return new CachesDescriptor(caches);
}
private Collection<? extends CacheDescriptor> getCacheDescriptors(String cacheManager,
Collection<String> cacheNames) {
return cacheNames.stream().map(cacheName -> new CacheDescriptor(cacheName, cacheManager)).collect(toList());
}
@DeleteOperation
public void clearCaches(@Nullable String cacheManager, @Nullable String cacheName) {
if (cacheManager == null) {
this.context.getBeansOfType(CacheManager.class)
.forEach((name, manager) -> this.clearCaches(manager, cacheName));
} else {
this.clearCaches(this.context.getBean(cacheManager, CacheManager.class), cacheName);
}
}
private void clearCaches(CacheManager cacheManager, String cacheName) {
if (cacheName == null) {
cacheManager.getCacheNames().forEach(cn -> cacheManager.getCache(cn).clear());
} else {
Cache cache = cacheManager.getCache(cacheName);
if (cache != null) {
cache.clear();
}
}
}
/**
* Description of an application context's caches, primarily
* intended for serialization to JSON.
*/
public static final class CachesDescriptor {
private final List<CacheDescriptor> caches;
private CachesDescriptor(List<CacheDescriptor> caches) {
this.caches = caches;
}
public List<CacheDescriptor> getCaches() {
return this.caches;
}
}
/**
* Description of a {@link Cache}, primarily intended for serialization to
* JSON.
*/
public static final class CacheDescriptor {
private final String name;
private final String cacheManager;
public CacheDescriptor(String name, String cacheManager) {
this.name = name;
this.cacheManager = cacheManager;
}
public String getName() {
return name;
}
public String getCacheManager() {
return cacheManager;
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2018 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
*
* http://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.
*/
/**
* Actuator support for caches.
*/
package org.springframework.boot.actuate.cache;

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2012-2018 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
*
* http://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.cache;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CachesEndpoint}.
*
* @author Johannes Edmeier
*/
public class CachesEndpointTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(CacheAutoConfiguration.class));
@Test
public void cacheReportIsReturned() {
//@formatter:off
this.contextRunner.withUserConfiguration(Config.class)
.run(context -> assertThat(context.getBean(CachesEndpoint.class).caches().getCaches())
.hasSize(2)
.anySatisfy(cache -> {
assertThat(cache.getName()).isEqualTo("first");
assertThat(cache.getCacheManager()).isEqualTo("cacheManager");
}).anySatisfy(cache -> {
assertThat(cache.getName()).isEqualTo("second");
assertThat(cache.getCacheManager()).isEqualTo("cacheManager");
})
);
//@formatter:on
}
@Test
public void cacheIsCleared() {
this.contextRunner.withUserConfiguration(Config.class).run((context) -> {
Cache firstCache = context.getBean("firstCache", Cache.class);
firstCache.put("key", "vale");
Cache secondCache = context.getBean("secondCache", Cache.class);
secondCache.put("key", "value");
context.getBean(CachesEndpoint.class).clearCaches(null, null);
assertThat(firstCache.get("key", String.class)).isNull();
assertThat(secondCache.get("key", String.class)).isNull();
});
}
@Test
public void namedCacheIsCleared() {
this.contextRunner.withUserConfiguration(Config.class).run((context) -> {
Cache firstCache = context.getBean("firstCache", Cache.class);
firstCache.put("key", "value");
Cache secondCache = context.getBean("secondCache", Cache.class);
secondCache.put("key", "value");
context.getBean(CachesEndpoint.class).clearCaches(null, "first");
assertThat(firstCache.get("key", String.class)).isNull();
assertThat(secondCache.get("key", String.class)).isEqualTo("value");
});
}
@Test
public void unknwonCache() {
this.contextRunner.withUserConfiguration(Config.class).run((context) -> {
Cache firstCache = context.getBean("firstCache", Cache.class);
firstCache.put("key", "value");
Cache secondCache = context.getBean("secondCache", Cache.class);
secondCache.put("key", "value");
context.getBean(CachesEndpoint.class).clearCaches(null, "UNKNWON");
assertThat(firstCache.get("key", String.class)).isEqualTo("value");
assertThat(secondCache.get("key", String.class)).isEqualTo("value");
});
}
@Configuration
@EnableCaching
public static class Config {
@Bean
public Cache firstCache() {
return new ConcurrentMapCache("first");
}
@Bean
public Cache secondCache() {
return new ConcurrentMapCache("second");
}
@Bean
public CachesEndpoint endpoint(ApplicationContext context) {
return new CachesEndpoint(context);
}
}
}