Polish "Introduce HealthIndicatorRegistry"

See gh-4965

Co-authored-by: Andy Wilkinson <awilkinson@pivotal.io>
pull/13197/head
Stephane Nicoll 7 years ago
parent d829d522be
commit 95b251590e

@ -20,8 +20,10 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;
import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
/** /**
@ -42,11 +44,10 @@ public abstract class CompositeHealthIndicatorConfiguration<H extends HealthIndi
if (beans.size() == 1) { if (beans.size() == 1) {
return createHealthIndicator(beans.values().iterator().next()); return createHealthIndicator(beans.values().iterator().next());
} }
CompositeHealthIndicator composite = new CompositeHealthIndicator( HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry();
this.healthAggregator); beans.forEach(
beans.forEach((name, source) -> composite.addHealthIndicator(name, (name, source) -> registry.register(name, createHealthIndicator(source)));
createHealthIndicator(source))); return new CompositeHealthIndicator(this.healthAggregator, registry);
return composite;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

@ -16,13 +16,13 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry; import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -30,27 +30,18 @@ import org.springframework.context.annotation.Configuration;
* Configuration for {@link HealthEndpoint}. * Configuration for {@link HealthEndpoint}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Vedran Pavic
*/ */
@Configuration @Configuration
@ConditionalOnSingleCandidate(HealthIndicatorRegistry.class)
class HealthEndpointConfiguration { class HealthEndpointConfiguration {
private final HealthAggregator healthAggregator;
private final HealthIndicatorRegistry healthIndicatorRegistry;
HealthEndpointConfiguration(ObjectProvider<HealthAggregator> healthAggregator,
ObjectProvider<HealthIndicatorRegistry> healthIndicatorRegistry) {
this.healthAggregator = healthAggregator
.getIfAvailable(OrderedHealthAggregator::new);
this.healthIndicatorRegistry = healthIndicatorRegistry.getObject();
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
public HealthEndpoint healthEndpoint() { public HealthEndpoint healthEndpoint(HealthAggregator healthAggregator,
return new HealthEndpoint(this.healthAggregator, this.healthIndicatorRegistry); HealthIndicatorRegistry registry) {
return new HealthEndpoint(
new CompositeHealthIndicator(healthAggregator, registry));
} }
} }

@ -16,11 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;
import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry; import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
@ -32,7 +28,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s. * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s.
@ -73,30 +68,7 @@ public class HealthIndicatorAutoConfiguration {
@ConditionalOnMissingBean(HealthIndicatorRegistry.class) @ConditionalOnMissingBean(HealthIndicatorRegistry.class)
public HealthIndicatorRegistry healthIndicatorRegistry( public HealthIndicatorRegistry healthIndicatorRegistry(
ApplicationContext applicationContext) { ApplicationContext applicationContext) {
HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); return HealthIndicatorRegistryBeans.get(applicationContext);
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
new ReactiveHealthIndicators().get(applicationContext)
.forEach(indicators::putIfAbsent);
}
indicators.forEach(registry::register);
return registry;
}
private static class ReactiveHealthIndicators {
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
return indicators;
}
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
return () -> indicator.health().block();
}
} }
} }

@ -0,0 +1,66 @@
/*
* 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.health;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.HealthIndicatorRegistryFactory;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ClassUtils;
/**
* Creates a {@link HealthIndicatorRegistry} from beans in the {@link ApplicationContext}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
final class HealthIndicatorRegistryBeans {
private HealthIndicatorRegistryBeans() {
}
public static HealthIndicatorRegistry get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
new ReactiveHealthIndicators().get(applicationContext)
.forEach(indicators::putIfAbsent);
}
HealthIndicatorRegistryFactory factory = new HealthIndicatorRegistryFactory();
return factory.createHealthIndicatorRegistry(indicators);
}
private static class ReactiveHealthIndicators {
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
return indicators;
}
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
return () -> indicator.health().block();
}
}
}

@ -34,9 +34,8 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -110,8 +109,7 @@ public class CloudFoundryWebEndpointDiscovererTests {
@Bean @Bean
public HealthEndpoint healthEndpoint() { public HealthEndpoint healthEndpoint() {
return new HealthEndpoint(mock(HealthAggregator.class), return new HealthEndpoint(mock(HealthIndicator.class));
mock(HealthIndicatorRegistry.class));
} }
@Bean @Bean

@ -23,10 +23,10 @@ import javax.sql.DataSource;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry; import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry; import org.springframework.boot.actuate.health.HealthIndicatorRegistryFactory;
import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator; import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
@ -73,9 +73,9 @@ public class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentati
@Bean @Bean
public HealthEndpoint endpoint(Map<String, HealthIndicator> healthIndicators) { public HealthEndpoint endpoint(Map<String, HealthIndicator> healthIndicators) {
HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); return new HealthEndpoint(new CompositeHealthIndicator(
healthIndicators.forEach(registry::register); new OrderedHealthAggregator(), new HealthIndicatorRegistryFactory()
return new HealthEndpoint(new OrderedHealthAggregator(), registry); .createHealthIndicatorRegistry(healthIndicators)));
} }
@Bean @Bean

@ -48,7 +48,8 @@ public class JmxEndpointIntegrationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class,
EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class,
HttpTraceAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)) HealthIndicatorAutoConfiguration.class,
HttpTraceAutoConfiguration.class))
.withConfiguration( .withConfiguration(
AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)); AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL));

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,8 +19,6 @@ package org.springframework.boot.actuate.health;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.util.Assert;
/** /**
* {@link HealthIndicator} that returns health indications from all registered delegates. * {@link HealthIndicator} that returns health indications from all registered delegates.
* *
@ -31,43 +29,71 @@ import org.springframework.util.Assert;
*/ */
public class CompositeHealthIndicator implements HealthIndicator { public class CompositeHealthIndicator implements HealthIndicator {
private final Map<String, HealthIndicator> indicators; private final HealthIndicatorRegistry registry;
private final HealthAggregator healthAggregator; private final HealthAggregator aggregator;
/** /**
* Create a new {@link CompositeHealthIndicator}. * Create a new {@link CompositeHealthIndicator}.
* @param healthAggregator the health aggregator * @param healthAggregator the health aggregator
* @deprecated since 2.1.0 in favour of
* {@link #CompositeHealthIndicator(HealthAggregator, HealthIndicatorRegistry)}
*/ */
@Deprecated
public CompositeHealthIndicator(HealthAggregator healthAggregator) { public CompositeHealthIndicator(HealthAggregator healthAggregator) {
this(healthAggregator, new LinkedHashMap<>()); this(healthAggregator, new DefaultHealthIndicatorRegistry());
} }
/** /**
* Create a new {@link CompositeHealthIndicator} from the specified indicators. * Create a new {@link CompositeHealthIndicator} from the specified
* indicators.
* @param healthAggregator the health aggregator * @param healthAggregator the health aggregator
* @param indicators a map of {@link HealthIndicator}s with the key being used as an * @param indicators a map of {@link HealthIndicator HealthIndicators} with
* indicator name. * the key being used as an indicator name.
* @deprecated since 2.1.0 in favour of
* {@link #CompositeHealthIndicator(HealthAggregator, HealthIndicatorRegistry)}
*/ */
@Deprecated
public CompositeHealthIndicator(HealthAggregator healthAggregator, public CompositeHealthIndicator(HealthAggregator healthAggregator,
Map<String, HealthIndicator> indicators) { Map<String, HealthIndicator> indicators) {
Assert.notNull(healthAggregator, "HealthAggregator must not be null"); this(healthAggregator, new DefaultHealthIndicatorRegistry(indicators));
Assert.notNull(indicators, "Indicators must not be null");
this.indicators = new LinkedHashMap<>(indicators);
this.healthAggregator = healthAggregator;
} }
/**
* Create a new {@link CompositeHealthIndicator} from the indicators in the
* given {@code registry}.
* @param healthAggregator the health aggregator
* @param registry the registry of {@link HealthIndicator HealthIndicators}.
*/
public CompositeHealthIndicator(HealthAggregator healthAggregator,
HealthIndicatorRegistry registry) {
this.aggregator = healthAggregator;
this.registry = registry;
}
/**
* Adds the given {@code healthIndicator}, associating it with the given
* {@code name}.
* @param name the name of the indicator
* @param indicator the indicator
* @throws IllegalStateException if an indicator with the given {@code name}
* is already registered.
* @deprecated since 2.1.0 in favour of
* {@link HealthIndicatorRegistry#register(String, HealthIndicator)}
*/
@Deprecated
public void addHealthIndicator(String name, HealthIndicator indicator) { public void addHealthIndicator(String name, HealthIndicator indicator) {
this.indicators.put(name, indicator); this.registry.register(name, indicator);
} }
@Override @Override
public Health health() { public Health health() {
Map<String, Health> healths = new LinkedHashMap<>(); Map<String, Health> healths = new LinkedHashMap<>();
for (Map.Entry<String, HealthIndicator> entry : this.indicators.entrySet()) { for (Map.Entry<String, HealthIndicator> entry : this.registry.getAll()
.entrySet()) {
healths.put(entry.getKey(), entry.getValue().health()); healths.put(entry.getKey(), entry.getValue().health());
} }
return this.healthAggregator.aggregate(healths); return this.aggregator.aggregate(healths);
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,20 +26,23 @@ import org.springframework.util.Assert;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
* @deprecated since 2.1.0 in favor of
* {@link CompositeHealthIndicator#CompositeHealthIndicator(HealthAggregator, HealthIndicatorRegistry)}
*/ */
@Deprecated
public class CompositeHealthIndicatorFactory { public class CompositeHealthIndicatorFactory {
private final Function<String, String> healthIndicatorNameFactory; private final Function<String, String> healthIndicatorNameFactory;
public CompositeHealthIndicatorFactory() {
this(new HealthIndicatorNameFactory());
}
public CompositeHealthIndicatorFactory( public CompositeHealthIndicatorFactory(
Function<String, String> healthIndicatorNameFactory) { Function<String, String> healthIndicatorNameFactory) {
this.healthIndicatorNameFactory = healthIndicatorNameFactory; this.healthIndicatorNameFactory = healthIndicatorNameFactory;
} }
public CompositeHealthIndicatorFactory() {
this(new HealthIndicatorNameFactory());
}
/** /**
* Create a {@link CompositeHealthIndicator} based on the specified health indicators. * Create a {@link CompositeHealthIndicator} based on the specified health indicators.
* @param healthAggregator the {@link HealthAggregator} * @param healthAggregator the {@link HealthAggregator}
@ -52,13 +55,10 @@ public class CompositeHealthIndicatorFactory {
Map<String, HealthIndicator> healthIndicators) { Map<String, HealthIndicator> healthIndicators) {
Assert.notNull(healthAggregator, "HealthAggregator must not be null"); Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null"); Assert.notNull(healthIndicators, "HealthIndicators must not be null");
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator( HealthIndicatorRegistryFactory factory = new HealthIndicatorRegistryFactory(
healthAggregator); this.healthIndicatorNameFactory);
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) { return new CompositeHealthIndicator(
String name = this.healthIndicatorNameFactory.apply(entry.getKey()); healthAggregator, factory.createHealthIndicatorRegistry(healthIndicators));
healthIndicator.addHealthIndicator(name, entry.getValue());
}
return healthIndicator;
} }
} }

@ -17,7 +17,7 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -26,42 +26,68 @@ import org.springframework.util.Assert;
* Default implementation of {@link HealthIndicatorRegistry}. * Default implementation of {@link HealthIndicatorRegistry}.
* *
* @author Vedran Pavic * @author Vedran Pavic
* @author Stephane Nicoll
* @since 2.1.0 * @since 2.1.0
*/ */
public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry { public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry {
private final Map<String, HealthIndicator> healthIndicators = new HashMap<>(); private final Object monitor = new Object();
private final Map<String, HealthIndicator> healthIndicators;
/**
* Create a new {@link DefaultHealthIndicatorRegistry}.
*/
public DefaultHealthIndicatorRegistry() {
this(new LinkedHashMap<>());
}
/**
* Create a new {@link DefaultHealthIndicatorRegistry} from the specified
* indicators.
* @param healthIndicators a map of {@link HealthIndicator}s with the key
* being used as an indicator name.
*/
public DefaultHealthIndicatorRegistry(Map<String, HealthIndicator> healthIndicators) {
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
this.healthIndicators = new LinkedHashMap<>(healthIndicators);
}
@Override @Override
public void register(String name, HealthIndicator healthIndicator) { public void register(String name, HealthIndicator healthIndicator) {
Assert.notNull(healthIndicator, "HealthIndicator must not be null"); Assert.notNull(healthIndicator, "HealthIndicator must not be null");
synchronized (this.healthIndicators) { Assert.notNull(name, "Name must not be null");
if (this.healthIndicators.get(name) != null) { synchronized (this.monitor) {
HealthIndicator existing = this.healthIndicators.putIfAbsent(name,
healthIndicator);
if (existing != null) {
throw new IllegalStateException( throw new IllegalStateException(
"HealthIndicator with name '" + name + "' already registered"); "HealthIndicator with name '" + name + "' already registered");
} }
this.healthIndicators.put(name, healthIndicator);
} }
} }
@Override @Override
public HealthIndicator unregister(String name) { public HealthIndicator unregister(String name) {
synchronized (this.healthIndicators) { Assert.notNull(name, "Name must not be null");
synchronized (this.monitor) {
return this.healthIndicators.remove(name); return this.healthIndicators.remove(name);
} }
} }
@Override @Override
public HealthIndicator get(String name) { public HealthIndicator get(String name) {
synchronized (this.healthIndicators) { Assert.notNull(name, "Name must not be null");
synchronized (this.monitor) {
return this.healthIndicators.get(name); return this.healthIndicators.get(name);
} }
} }
@Override @Override
public Map<String, HealthIndicator> getAll() { public Map<String, HealthIndicator> getAll() {
synchronized (this.healthIndicators) { synchronized (this.monitor) {
return Collections.unmodifiableMap(new HashMap<>(this.healthIndicators)); return Collections
.unmodifiableMap(new LinkedHashMap<>(this.healthIndicators));
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,8 +19,7 @@ package org.springframework.boot.actuate.health;
import java.util.Map; import java.util.Map;
/** /**
* Strategy interface used by {@link CompositeHealthIndicator} to aggregate {@link Health} * Strategy interface used to aggregate {@link Health} instances into a final one.
* instances into a final one.
* <p> * <p>
* This is especially useful to combine subsystem states expressed through * This is especially useful to combine subsystem states expressed through
* {@link Health#getStatus()} into one state for the entire system. The default * {@link Health#getStatus()} into one state for the entire system. The default

@ -26,35 +26,26 @@ import org.springframework.util.Assert;
* @author Dave Syer * @author Dave Syer
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Vedran Pavic
* @since 2.0.0 * @since 2.0.0
*/ */
@Endpoint(id = "health") @Endpoint(id = "health")
public class HealthEndpoint { public class HealthEndpoint {
private final HealthAggregator healthAggregator; private final HealthIndicator healthIndicator;
private final HealthIndicatorRegistry healthIndicatorRegistry;
/** /**
* Create a new {@link HealthEndpoint} instance. * Create a new {@link HealthEndpoint} instance that will use the given
* @param healthAggregator the health aggregator * {@code healthIndicator} to generate its response.
* @param healthIndicatorRegistry the health indicator registry * @param healthIndicator the health indicator
*/ */
public HealthEndpoint(HealthAggregator healthAggregator, public HealthEndpoint(HealthIndicator healthIndicator) {
HealthIndicatorRegistry healthIndicatorRegistry) { Assert.notNull(healthIndicator, "HealthIndicator must not be null");
Assert.notNull(healthAggregator, "healthAggregator must not be null"); this.healthIndicator = healthIndicator;
Assert.notNull(healthIndicatorRegistry, "healthIndicatorRegistry must not be null");
this.healthAggregator = healthAggregator;
this.healthIndicatorRegistry = healthIndicatorRegistry;
} }
@ReadOperation @ReadOperation
public Health health() { public Health health() {
CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory(); return this.healthIndicator.health();
CompositeHealthIndicator healthIndicator = factory.createHealthIndicator(
this.healthAggregator, this.healthIndicatorRegistry.getAll());
return healthIndicator.health();
} }
} }

@ -19,46 +19,48 @@ package org.springframework.boot.actuate.health;
import java.util.Map; import java.util.Map;
/** /**
* A registry of {@link HealthIndicator}s. * A registry of {@link HealthIndicator HealthIndicators}.
* <p> * <p>
* Implementations <strong>must</strong> be thread-safe. * Implementations <strong>must</strong> be thread-safe.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Vedran Pavic * @author Vedran Pavic
* @author Stephane Nicoll
* @since 2.1.0 * @since 2.1.0
*/ */
public interface HealthIndicatorRegistry { public interface HealthIndicatorRegistry {
/** /**
* Registers the given {@code healthIndicator}, associating it with the given * Registers the given {@code healthIndicator}, associating it with the
* {@code name}. * given {@code name}.
* @param name the name of the indicator * @param name the name of the indicator
* @param healthIndicator the indicator * @param healthIndicator the indicator
* @throws IllegalStateException if an indicator with the given {@code name} is * @throws IllegalStateException if an indicator with the given {@code name}
* already registered. * is already registered.
*/ */
void register(String name, HealthIndicator healthIndicator); void register(String name, HealthIndicator healthIndicator);
/** /**
* Unregisters the {@code HealthIndicator} previously registered with the given * Unregisters the {@code HealthIndicator} previously registered with the
* {@code name}. * given {@code name}.
* @param name the name of the indicator * @param name the name of the indicator
* @return the unregistered indicator, or {@code null} if no indicator was found in * @return the unregistered indicator, or {@code null} if no indicator was
* the registry for the given {@code name}. * found in the registry for the given {@code name}.
*/ */
HealthIndicator unregister(String name); HealthIndicator unregister(String name);
/** /**
* Returns the health indicator registered with the given {@code name}. * Returns the health indicator registered with the given {@code name}.
* @param name the name of the indicator * @param name the name of the indicator
* @return the health indicator, or {@code null} if no indicator was registered with * @return the health indicator, or {@code null} if no indicator was
* the given {@code name}. * registered with the given {@code name}.
*/ */
HealthIndicator get(String name); HealthIndicator get(String name);
/** /**
* Returns a snapshot of the registered health indicators and their names. The * Returns a snapshot of the registered health indicators and their names.
* contents of the map do not reflect subsequent changes to the registry. * The contents of the map do not reflect subsequent changes to the
* registry.
* @return the snapshot of registered health indicators * @return the snapshot of registered health indicators
*/ */
Map<String, HealthIndicator> getAll(); Map<String, HealthIndicator> getAll();

@ -0,0 +1,66 @@
/*
* 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.health;
import java.util.Map;
import java.util.function.Function;
import org.springframework.util.Assert;
/**
* Factory to create a {@link HealthIndicatorRegistry}.
*
* @author Stephane Nicoll
* @since 2.1.0
*/
public class HealthIndicatorRegistryFactory {
private final Function<String, String> healthIndicatorNameFactory;
public HealthIndicatorRegistryFactory(
Function<String, String> healthIndicatorNameFactory) {
this.healthIndicatorNameFactory = healthIndicatorNameFactory;
}
public HealthIndicatorRegistryFactory() {
this(new HealthIndicatorNameFactory());
}
/**
* Create a {@link HealthIndicatorRegistry} based on the specified health
* indicators.
* @param healthIndicators the {@link HealthIndicator} instances mapped by
* name
* @return a {@link HealthIndicator} that delegates to the specified
* {@code healthIndicators}.
*/
public HealthIndicatorRegistry createHealthIndicatorRegistry(
Map<String, HealthIndicator> healthIndicators) {
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
return initialize(new DefaultHealthIndicatorRegistry(), healthIndicators);
}
protected <T extends HealthIndicatorRegistry> T initialize(T registry,
Map<String, HealthIndicator> healthIndicators) {
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
String name = this.healthIndicatorNameFactory.apply(entry.getKey());
registry.register(name, entry.getValue());
}
return registry;
}
}

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@Deprecated
public class CompositeHealthIndicatorFactoryTests { public class CompositeHealthIndicatorFactoryTests {
@Test @Test

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -45,9 +46,6 @@ public class CompositeHealthIndicatorTests {
@Mock @Mock
private HealthIndicator two; private HealthIndicator two;
@Mock
private HealthIndicator three;
@Before @Before
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
@ -55,8 +53,6 @@ public class CompositeHealthIndicatorTests {
.willReturn(new Health.Builder().unknown().withDetail("1", "1").build()); .willReturn(new Health.Builder().unknown().withDetail("1", "1").build());
given(this.two.health()) given(this.two.health())
.willReturn(new Health.Builder().unknown().withDetail("2", "2").build()); .willReturn(new Health.Builder().unknown().withDetail("2", "2").build());
given(this.three.health())
.willReturn(new Health.Builder().unknown().withDetail("3", "3").build());
this.healthAggregator = new OrderedHealthAggregator(); this.healthAggregator = new OrderedHealthAggregator();
} }
@ -67,7 +63,7 @@ public class CompositeHealthIndicatorTests {
indicators.put("one", this.one); indicators.put("one", this.one);
indicators.put("two", this.two); indicators.put("two", this.two);
CompositeHealthIndicator composite = new CompositeHealthIndicator( CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator, indicators); this.healthAggregator, new DefaultHealthIndicatorRegistry(indicators));
Health result = composite.health(); Health result = composite.health();
assertThat(result.getDetails()).hasSize(2); assertThat(result.getDetails()).hasSize(2);
assertThat(result.getDetails()).containsEntry("one", assertThat(result.getDetails()).containsEntry("one",
@ -76,48 +72,16 @@ public class CompositeHealthIndicatorTests {
new Health.Builder().unknown().withDetail("2", "2").build()); new Health.Builder().unknown().withDetail("2", "2").build());
} }
@Test
public void createWithIndicatorsAndAdd() {
Map<String, HealthIndicator> indicators = new HashMap<>();
indicators.put("one", this.one);
indicators.put("two", this.two);
CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator, indicators);
composite.addHealthIndicator("three", this.three);
Health result = composite.health();
assertThat(result.getDetails()).hasSize(3);
assertThat(result.getDetails()).containsEntry("one",
new Health.Builder().unknown().withDetail("1", "1").build());
assertThat(result.getDetails()).containsEntry("two",
new Health.Builder().unknown().withDetail("2", "2").build());
assertThat(result.getDetails()).containsEntry("three",
new Health.Builder().unknown().withDetail("3", "3").build());
}
@Test
public void createWithoutAndAdd() {
CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
composite.addHealthIndicator("one", this.one);
composite.addHealthIndicator("two", this.two);
Health result = composite.health();
assertThat(result.getDetails().size()).isEqualTo(2);
assertThat(result.getDetails()).containsEntry("one",
new Health.Builder().unknown().withDetail("1", "1").build());
assertThat(result.getDetails()).containsEntry("two",
new Health.Builder().unknown().withDetail("2", "2").build());
}
@Test @Test
public void testSerialization() throws Exception { public void testSerialization() throws Exception {
Map<String, HealthIndicator> indicators = new HashMap<>(); Map<String, HealthIndicator> indicators = new HashMap<>();
indicators.put("db1", this.one); indicators.put("db1", this.one);
indicators.put("db2", this.two); indicators.put("db2", this.two);
CompositeHealthIndicator innerComposite = new CompositeHealthIndicator( CompositeHealthIndicator innerComposite = new CompositeHealthIndicator(
this.healthAggregator, indicators); this.healthAggregator, new DefaultHealthIndicatorRegistry(indicators));
CompositeHealthIndicator composite = new CompositeHealthIndicator( CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator); this.healthAggregator, new DefaultHealthIndicatorRegistry(
composite.addHealthIndicator("db", innerComposite); Collections.singletonMap("db", innerComposite)));
Health result = composite.health(); Health result = composite.health();
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
assertThat(mapper.writeValueAsString(result)).isEqualTo( assertThat(mapper.writeValueAsString(result)).isEqualTo(

@ -16,6 +16,8 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
import java.util.Map;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -29,8 +31,9 @@ import static org.mockito.Mockito.mock;
* Tests for {@link DefaultHealthIndicatorRegistry}. * Tests for {@link DefaultHealthIndicatorRegistry}.
* *
* @author Vedran Pavic * @author Vedran Pavic
* @author Stephane Nicoll
*/ */
public class DefaultHealthIndicatorRegistryTest { public class DefaultHealthIndicatorRegistryTests {
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
@ -43,9 +46,10 @@ public class DefaultHealthIndicatorRegistryTest {
@Before @Before
public void setUp() { public void setUp() {
given(this.one.health()).willReturn(new Health.Builder().up().build()); given(this.one.health())
given(this.two.health()).willReturn(new Health.Builder().unknown().build()); .willReturn(new Health.Builder().unknown().withDetail("1", "1").build());
given(this.two.health())
.willReturn(new Health.Builder().unknown().withDetail("2", "2").build());
this.registry = new DefaultHealthIndicatorRegistry(); this.registry = new DefaultHealthIndicatorRegistry();
} }
@ -60,9 +64,9 @@ public class DefaultHealthIndicatorRegistryTest {
@Test @Test
public void registerAlreadyUsedName() { public void registerAlreadyUsedName() {
this.registry.register("one", this.one);
this.thrown.expect(IllegalStateException.class); this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("HealthIndicator with name 'one' already registered"); this.thrown.expectMessage("HealthIndicator with name 'one' already registered");
this.registry.register("one", this.one);
this.registry.register("one", this.two); this.registry.register("one", this.two);
} }
@ -77,7 +81,7 @@ public class DefaultHealthIndicatorRegistryTest {
} }
@Test @Test
public void unregisterNotKnown() { public void unregisterUnknown() {
this.registry.register("one", this.one); this.registry.register("one", this.one);
assertThat(this.registry.getAll()).hasSize(1); assertThat(this.registry.getAll()).hasSize(1);
HealthIndicator two = this.registry.unregister("two"); HealthIndicator two = this.registry.unregister("two");
@ -85,4 +89,22 @@ public class DefaultHealthIndicatorRegistryTest {
assertThat(this.registry.getAll()).hasSize(1); assertThat(this.registry.getAll()).hasSize(1);
} }
@Test
public void getAllIsASnapshot() {
this.registry.register("one", this.one);
Map<String, HealthIndicator> snapshot = this.registry.getAll();
assertThat(snapshot).containsOnlyKeys("one");
this.registry.register("two", this.two);
assertThat(snapshot).containsOnlyKeys("one");
}
@Test
public void getAllIsImmutable() {
this.registry.register("one", this.one);
Map<String, HealthIndicator> snapshot = this.registry.getAll();
this.thrown.expect(UnsupportedOperationException.class);
snapshot.clear();
}
} }

@ -30,7 +30,6 @@ import static org.assertj.core.api.Assertions.entry;
* @author Phillip Webb * @author Phillip Webb
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Vedran Pavic
*/ */
public class HealthEndpointTests { public class HealthEndpointTests {
@ -41,8 +40,8 @@ public class HealthEndpointTests {
.withDetail("first", "1").build()); .withDetail("first", "1").build());
healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP) healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP)
.withDetail("second", "2").build()); .withDetail("second", "2").build());
HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(), HealthEndpoint endpoint = new HealthEndpoint(
createHealthIndicatorRegistry(healthIndicators)); createHealthIndicator(healthIndicators));
Health health = endpoint.health(); Health health = endpoint.health();
assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsOnlyKeys("up", "upAgain"); assertThat(health.getDetails()).containsOnlyKeys("up", "upAgain");
@ -52,11 +51,10 @@ public class HealthEndpointTests {
assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2")); assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2"));
} }
private HealthIndicatorRegistry createHealthIndicatorRegistry( private HealthIndicator createHealthIndicator(
Map<String, HealthIndicator> healthIndicators) { Map<String, HealthIndicator> healthIndicators) {
HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); return new CompositeHealthIndicator(new OrderedHealthAggregator(),
healthIndicators.forEach(registry::register); new DefaultHealthIndicatorRegistry(healthIndicators));
return registry;
} }
} }

@ -35,7 +35,6 @@ import org.springframework.test.web.reactive.server.WebTestClient;
* exposed by Jersey, Spring MVC, and WebFlux. * exposed by Jersey, Spring MVC, and WebFlux.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Vedran Pavic
*/ */
@RunWith(WebEndpointRunners.class) @RunWith(WebEndpointRunners.class)
public class HealthEndpointWebIntegrationTests { public class HealthEndpointWebIntegrationTests {
@ -53,23 +52,48 @@ public class HealthEndpointWebIntegrationTests {
@Test @Test
public void whenHealthIsDown503ResponseIsReturned() { public void whenHealthIsDown503ResponseIsReturned() {
context.getBean("alphaHealthIndicator", TestHealthIndicator.class) HealthIndicatorRegistry registry = context.getBean(HealthIndicatorRegistry.class);
.setHealth(Health.down().build()); registry.register("charlie", () -> Health.down().build());
client.get().uri("/actuator/health").exchange().expectStatus() try {
.isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status") client.get().uri("/actuator/health").exchange().expectStatus()
.isEqualTo("DOWN").jsonPath("details.alpha.status").isEqualTo("DOWN") .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status")
.jsonPath("details.bravo.status").isEqualTo("UP"); .isEqualTo("DOWN").jsonPath("details.alpha.status").isEqualTo("UP")
.jsonPath("details.bravo.status").isEqualTo("UP")
.jsonPath("details.charlie.status").isEqualTo("DOWN");
}
finally {
registry.unregister("charlie");
}
}
@Test
public void whenHealthIndicatorIsRemovedResponseIsAltered() {
HealthIndicatorRegistry registry = context.getBean(HealthIndicatorRegistry.class);
HealthIndicator bravo = registry.unregister("bravo");
try {
client.get().uri("/actuator/health").exchange().expectStatus().isOk().expectBody()
.jsonPath("status").isEqualTo("UP").jsonPath("details.alpha.status")
.isEqualTo("UP").jsonPath("details.bravo.status").doesNotExist();
}
finally {
registry.register("bravo", bravo);
}
} }
@Configuration @Configuration
public static class TestConfiguration { public static class TestConfiguration {
@Bean @Bean
public HealthEndpoint healthEndpoint( public HealthIndicatorRegistry healthIndicatorFactory(
Map<String, HealthIndicator> healthIndicators) { Map<String, HealthIndicator> healthIndicators) {
HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); return new HealthIndicatorRegistryFactory()
healthIndicators.forEach(registry::register); .createHealthIndicatorRegistry(healthIndicators);
return new HealthEndpoint(new OrderedHealthAggregator(), registry); }
@Bean
public HealthEndpoint healthEndpoint(HealthIndicatorRegistry registry) {
return new HealthEndpoint(new CompositeHealthIndicator(
new OrderedHealthAggregator(), registry));
} }
@Bean @Bean
@ -82,30 +106,13 @@ public class HealthEndpointWebIntegrationTests {
} }
@Bean @Bean
public TestHealthIndicator alphaHealthIndicator() { public HealthIndicator alphaHealthIndicator() {
return new TestHealthIndicator(); return () -> Health.up().build();
} }
@Bean @Bean
public TestHealthIndicator bravoHealthIndicator() { public HealthIndicator bravoHealthIndicator() {
return new TestHealthIndicator(); return () -> Health.up().build();
}
}
private static class TestHealthIndicator implements HealthIndicator {
private Health health = Health.up().build();
@Override
public Health health() {
Health result = this.health;
this.health = Health.up().build();
return result;
}
void setHealth(Health health) {
this.health = health;
} }
} }

@ -727,9 +727,9 @@ unauthenticated users.
Health information is collected from all Health information is collected from all
{sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] instances {sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] instances
registered with {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[`HealthIndicatorRegistry`]. registered with {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[
Spring Boot includes a number of auto-configured `HealthIndicators` and you can also write `HealthIndicatorRegistry`]. Spring Boot includes a number of auto-configured
your own. By default, the final system state is `HealthIndicators` and you can also write your own. By default, the final system state is
derived by the `HealthAggregator` which sorts the statuses from each `HealthIndicator` derived by the `HealthAggregator` which sorts the statuses from each `HealthIndicator`
based on an ordered list of statuses. The first status in the sorted list is used as the based on an ordered list of statuses. The first status in the sorted list is used as the
overall health status. If no `HealthIndicator` returns a status that is known to the overall health status. If no `HealthIndicator` returns a status that is known to the
@ -820,7 +820,8 @@ NOTE: The identifier for a given `HealthIndicator` is the name of the bean witho
is available in an entry named `my`. is available in an entry named `my`.
Additionally, you can register (and unregister) `HealthIndicator` instances in runtime Additionally, you can register (and unregister) `HealthIndicator` instances in runtime
using {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[`HealthIndicatorRegistry`]. using {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[
`HealthIndicatorRegistry`].
In addition to Spring Boot's predefined In addition to Spring Boot's predefined
{sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] types, it is also possible for {sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] types, it is also possible for

Loading…
Cancel
Save