diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java index e232e7007a..6e0cc0e110 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java @@ -20,8 +20,10 @@ import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; 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.HealthIndicator; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; import org.springframework.core.ResolvableType; /** @@ -42,11 +44,10 @@ public abstract class CompositeHealthIndicatorConfiguration composite.addHealthIndicator(name, - createHealthIndicator(source))); - return composite; + HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); + beans.forEach( + (name, source) -> registry.register(name, createHealthIndicator(source))); + return new CompositeHealthIndicator(this.healthAggregator, registry); } @SuppressWarnings("unchecked") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java index b059bd6451..9bb7af2fee 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java @@ -16,13 +16,13 @@ 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.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthEndpoint; 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.ConditionalOnSingleCandidate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,27 +30,18 @@ import org.springframework.context.annotation.Configuration; * Configuration for {@link HealthEndpoint}. * * @author Stephane Nicoll - * @author Vedran Pavic */ @Configuration +@ConditionalOnSingleCandidate(HealthIndicatorRegistry.class) class HealthEndpointConfiguration { - private final HealthAggregator healthAggregator; - - private final HealthIndicatorRegistry healthIndicatorRegistry; - - HealthEndpointConfiguration(ObjectProvider healthAggregator, - ObjectProvider healthIndicatorRegistry) { - this.healthAggregator = healthAggregator - .getIfAvailable(OrderedHealthAggregator::new); - this.healthIndicatorRegistry = healthIndicatorRegistry.getObject(); - } - @Bean @ConditionalOnMissingBean @ConditionalOnEnabledEndpoint - public HealthEndpoint healthEndpoint() { - return new HealthEndpoint(this.healthAggregator, this.healthIndicatorRegistry); + public HealthEndpoint healthEndpoint(HealthAggregator healthAggregator, + HealthIndicatorRegistry registry) { + return new HealthEndpoint( + new CompositeHealthIndicator(healthAggregator, registry)); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java index 5e27db3b3e..5be8f3ab13 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java @@ -16,11 +16,7 @@ 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.DefaultHealthIndicatorRegistry; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; 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.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.util.ClassUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s. @@ -73,30 +68,7 @@ public class HealthIndicatorAutoConfiguration { @ConditionalOnMissingBean(HealthIndicatorRegistry.class) public HealthIndicatorRegistry healthIndicatorRegistry( ApplicationContext applicationContext) { - HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); - Map 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 get(ApplicationContext applicationContext) { - Map 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(); - } - + return HealthIndicatorRegistryBeans.get(applicationContext); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryBeans.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryBeans.java new file mode 100644 index 0000000000..87b3a103b8 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryBeans.java @@ -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 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 get(ApplicationContext applicationContext) { + Map 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(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java index 085fabe106..31acdc8034 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java @@ -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.WebOperation; 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.HealthIndicatorRegistry; +import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -110,8 +109,7 @@ public class CloudFoundryWebEndpointDiscovererTests { @Bean public HealthEndpoint healthEndpoint() { - return new HealthEndpoint(mock(HealthAggregator.class), - mock(HealthIndicatorRegistry.class)); + return new HealthEndpoint(mock(HealthIndicator.class)); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java index 28036a283d..9d66ad8eac 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java @@ -23,10 +23,10 @@ import javax.sql.DataSource; 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.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.jdbc.DataSourceHealthIndicator; import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator; @@ -73,9 +73,9 @@ public class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentati @Bean public HealthEndpoint endpoint(Map healthIndicators) { - HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); - healthIndicators.forEach(registry::register); - return new HealthEndpoint(new OrderedHealthAggregator(), registry); + return new HealthEndpoint(new CompositeHealthIndicator( + new OrderedHealthAggregator(), new HealthIndicatorRegistryFactory() + .createHealthIndicatorRegistry(healthIndicators))); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java index 700f22962c..d81cda5521 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java @@ -48,7 +48,8 @@ public class JmxEndpointIntegrationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class, - HttpTraceAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)) + HealthIndicatorAutoConfiguration.class, + HttpTraceAutoConfiguration.class)) .withConfiguration( AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java index 5e5f4c2cbb..c7507c691a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java @@ -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"); * 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.Map; -import org.springframework.util.Assert; - /** * {@link HealthIndicator} that returns health indications from all registered delegates. * @@ -31,43 +29,71 @@ import org.springframework.util.Assert; */ public class CompositeHealthIndicator implements HealthIndicator { - private final Map indicators; + private final HealthIndicatorRegistry registry; - private final HealthAggregator healthAggregator; + private final HealthAggregator aggregator; /** * Create a new {@link CompositeHealthIndicator}. * @param healthAggregator the health aggregator + * @deprecated since 2.1.0 in favour of + * {@link #CompositeHealthIndicator(HealthAggregator, HealthIndicatorRegistry)} */ + @Deprecated 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 indicators a map of {@link HealthIndicator}s with the key being used as an - * indicator name. + * @param indicators a map of {@link HealthIndicator HealthIndicators} with + * 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, Map indicators) { - Assert.notNull(healthAggregator, "HealthAggregator must not be null"); - Assert.notNull(indicators, "Indicators must not be null"); - this.indicators = new LinkedHashMap<>(indicators); - this.healthAggregator = healthAggregator; + this(healthAggregator, new DefaultHealthIndicatorRegistry(indicators)); } + /** + * 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) { - this.indicators.put(name, indicator); + this.registry.register(name, indicator); } @Override public Health health() { Map healths = new LinkedHashMap<>(); - for (Map.Entry entry : this.indicators.entrySet()) { + for (Map.Entry entry : this.registry.getAll() + .entrySet()) { healths.put(entry.getKey(), entry.getValue().health()); } - return this.healthAggregator.aggregate(healths); + return this.aggregator.aggregate(healths); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactory.java index 17ab33f0be..8d0c44dac9 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactory.java @@ -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"); * you may not use this file except in compliance with the License. @@ -26,20 +26,23 @@ import org.springframework.util.Assert; * * @author Stephane Nicoll * @since 2.0.0 + * @deprecated since 2.1.0 in favor of + * {@link CompositeHealthIndicator#CompositeHealthIndicator(HealthAggregator, HealthIndicatorRegistry)} */ +@Deprecated public class CompositeHealthIndicatorFactory { private final Function healthIndicatorNameFactory; + public CompositeHealthIndicatorFactory() { + this(new HealthIndicatorNameFactory()); + } + public CompositeHealthIndicatorFactory( Function healthIndicatorNameFactory) { this.healthIndicatorNameFactory = healthIndicatorNameFactory; } - public CompositeHealthIndicatorFactory() { - this(new HealthIndicatorNameFactory()); - } - /** * Create a {@link CompositeHealthIndicator} based on the specified health indicators. * @param healthAggregator the {@link HealthAggregator} @@ -52,13 +55,10 @@ public class CompositeHealthIndicatorFactory { Map healthIndicators) { Assert.notNull(healthAggregator, "HealthAggregator must not be null"); Assert.notNull(healthIndicators, "HealthIndicators must not be null"); - CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator( - healthAggregator); - for (Map.Entry entry : healthIndicators.entrySet()) { - String name = this.healthIndicatorNameFactory.apply(entry.getKey()); - healthIndicator.addHealthIndicator(name, entry.getValue()); - } - return healthIndicator; + HealthIndicatorRegistryFactory factory = new HealthIndicatorRegistryFactory( + this.healthIndicatorNameFactory); + return new CompositeHealthIndicator( + healthAggregator, factory.createHealthIndicatorRegistry(healthIndicators)); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java index 55f528daa5..a129646047 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java @@ -17,7 +17,7 @@ package org.springframework.boot.actuate.health; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import org.springframework.util.Assert; @@ -26,42 +26,68 @@ import org.springframework.util.Assert; * Default implementation of {@link HealthIndicatorRegistry}. * * @author Vedran Pavic + * @author Stephane Nicoll * @since 2.1.0 */ public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry { - private final Map healthIndicators = new HashMap<>(); + private final Object monitor = new Object(); + + private final Map 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 healthIndicators) { + Assert.notNull(healthIndicators, "HealthIndicators must not be null"); + this.healthIndicators = new LinkedHashMap<>(healthIndicators); + } @Override public void register(String name, HealthIndicator healthIndicator) { Assert.notNull(healthIndicator, "HealthIndicator must not be null"); - synchronized (this.healthIndicators) { - if (this.healthIndicators.get(name) != null) { + Assert.notNull(name, "Name must not be null"); + synchronized (this.monitor) { + HealthIndicator existing = this.healthIndicators.putIfAbsent(name, + healthIndicator); + if (existing != null) { throw new IllegalStateException( "HealthIndicator with name '" + name + "' already registered"); } - this.healthIndicators.put(name, healthIndicator); } } @Override public HealthIndicator unregister(String name) { - synchronized (this.healthIndicators) { + Assert.notNull(name, "Name must not be null"); + synchronized (this.monitor) { return this.healthIndicators.remove(name); } } @Override public HealthIndicator get(String name) { - synchronized (this.healthIndicators) { + Assert.notNull(name, "Name must not be null"); + synchronized (this.monitor) { return this.healthIndicators.get(name); } } @Override public Map getAll() { - synchronized (this.healthIndicators) { - return Collections.unmodifiableMap(new HashMap<>(this.healthIndicators)); + synchronized (this.monitor) { + return Collections + .unmodifiableMap(new LinkedHashMap<>(this.healthIndicators)); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java index 63f340aa28..8ea9e12fc2 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java @@ -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"); * 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; /** - * Strategy interface used by {@link CompositeHealthIndicator} to aggregate {@link Health} - * instances into a final one. + * Strategy interface used to aggregate {@link Health} instances into a final one. *

* This is especially useful to combine subsystem states expressed through * {@link Health#getStatus()} into one state for the entire system. The default diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java index 59390b6a8b..69b0ee3355 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java @@ -26,35 +26,26 @@ import org.springframework.util.Assert; * @author Dave Syer * @author Christian Dupuis * @author Andy Wilkinson - * @author Vedran Pavic * @since 2.0.0 */ @Endpoint(id = "health") public class HealthEndpoint { - private final HealthAggregator healthAggregator; - - private final HealthIndicatorRegistry healthIndicatorRegistry; + private final HealthIndicator healthIndicator; /** - * Create a new {@link HealthEndpoint} instance. - * @param healthAggregator the health aggregator - * @param healthIndicatorRegistry the health indicator registry + * Create a new {@link HealthEndpoint} instance that will use the given + * {@code healthIndicator} to generate its response. + * @param healthIndicator the health indicator */ - public HealthEndpoint(HealthAggregator healthAggregator, - HealthIndicatorRegistry healthIndicatorRegistry) { - Assert.notNull(healthAggregator, "healthAggregator must not be null"); - Assert.notNull(healthIndicatorRegistry, "healthIndicatorRegistry must not be null"); - this.healthAggregator = healthAggregator; - this.healthIndicatorRegistry = healthIndicatorRegistry; + public HealthEndpoint(HealthIndicator healthIndicator) { + Assert.notNull(healthIndicator, "HealthIndicator must not be null"); + this.healthIndicator = healthIndicator; } @ReadOperation public Health health() { - CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory(); - CompositeHealthIndicator healthIndicator = factory.createHealthIndicator( - this.healthAggregator, this.healthIndicatorRegistry.getAll()); - return healthIndicator.health(); + return this.healthIndicator.health(); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java index b499771bcb..cce483e4e9 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.java @@ -19,46 +19,48 @@ package org.springframework.boot.actuate.health; import java.util.Map; /** - * A registry of {@link HealthIndicator}s. + * A registry of {@link HealthIndicator HealthIndicators}. *

* Implementations must be thread-safe. * * @author Andy Wilkinson * @author Vedran Pavic + * @author Stephane Nicoll * @since 2.1.0 */ public interface HealthIndicatorRegistry { /** - * Registers the given {@code healthIndicator}, associating it with the given - * {@code name}. + * Registers the given {@code healthIndicator}, associating it with the + * given {@code name}. * @param name the name of the indicator * @param healthIndicator the indicator - * @throws IllegalStateException if an indicator with the given {@code name} is - * already registered. + * @throws IllegalStateException if an indicator with the given {@code name} + * is already registered. */ void register(String name, HealthIndicator healthIndicator); /** - * Unregisters the {@code HealthIndicator} previously registered with the given - * {@code name}. + * Unregisters the {@code HealthIndicator} previously registered with the + * given {@code name}. * @param name the name of the indicator - * @return the unregistered indicator, or {@code null} if no indicator was found in - * the registry for the given {@code name}. + * @return the unregistered indicator, or {@code null} if no indicator was + * found in the registry for the given {@code name}. */ HealthIndicator unregister(String name); /** * Returns the health indicator registered with the given {@code name}. * @param name the name of the indicator - * @return the health indicator, or {@code null} if no indicator was registered with - * the given {@code name}. + * @return the health indicator, or {@code null} if no indicator was + * registered with the given {@code name}. */ HealthIndicator get(String name); /** - * Returns a snapshot of the registered health indicators and their names. The - * contents of the map do not reflect subsequent changes to the registry. + * Returns a snapshot of the registered health indicators and their names. + * The contents of the map do not reflect subsequent changes to the + * registry. * @return the snapshot of registered health indicators */ Map getAll(); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java new file mode 100644 index 0000000000..6fff7a1ed2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java @@ -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 healthIndicatorNameFactory; + + public HealthIndicatorRegistryFactory( + Function 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 healthIndicators) { + Assert.notNull(healthIndicators, "HealthIndicators must not be null"); + return initialize(new DefaultHealthIndicatorRegistry(), healthIndicators); + } + + protected T initialize(T registry, + Map healthIndicators) { + for (Map.Entry entry : healthIndicators.entrySet()) { + String name = this.healthIndicatorNameFactory.apply(entry.getKey()); + registry.register(name, entry.getValue()); + } + return registry; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactoryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactoryTests.java index 869e1abe1f..0df3382d52 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactoryTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorFactoryTests.java @@ -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"); * 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 Andy Wilkinson */ +@Deprecated public class CompositeHealthIndicatorFactoryTests { @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java index af571da8f7..69778a0c54 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java @@ -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"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.health; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -45,9 +46,6 @@ public class CompositeHealthIndicatorTests { @Mock private HealthIndicator two; - @Mock - private HealthIndicator three; - @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -55,8 +53,6 @@ public class CompositeHealthIndicatorTests { .willReturn(new Health.Builder().unknown().withDetail("1", "1").build()); given(this.two.health()) .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(); } @@ -67,7 +63,7 @@ public class CompositeHealthIndicatorTests { indicators.put("one", this.one); indicators.put("two", this.two); CompositeHealthIndicator composite = new CompositeHealthIndicator( - this.healthAggregator, indicators); + this.healthAggregator, new DefaultHealthIndicatorRegistry(indicators)); Health result = composite.health(); assertThat(result.getDetails()).hasSize(2); assertThat(result.getDetails()).containsEntry("one", @@ -76,48 +72,16 @@ public class CompositeHealthIndicatorTests { new Health.Builder().unknown().withDetail("2", "2").build()); } - @Test - public void createWithIndicatorsAndAdd() { - Map 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 public void testSerialization() throws Exception { Map indicators = new HashMap<>(); indicators.put("db1", this.one); indicators.put("db2", this.two); CompositeHealthIndicator innerComposite = new CompositeHealthIndicator( - this.healthAggregator, indicators); + this.healthAggregator, new DefaultHealthIndicatorRegistry(indicators)); CompositeHealthIndicator composite = new CompositeHealthIndicator( - this.healthAggregator); - composite.addHealthIndicator("db", innerComposite); + this.healthAggregator, new DefaultHealthIndicatorRegistry( + Collections.singletonMap("db", innerComposite))); Health result = composite.health(); ObjectMapper mapper = new ObjectMapper(); assertThat(mapper.writeValueAsString(result)).isEqualTo( diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTest.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java similarity index 73% rename from spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTest.java rename to spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java index c24848c24e..58e6fdcee6 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTest.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.health; +import java.util.Map; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -29,8 +31,9 @@ import static org.mockito.Mockito.mock; * Tests for {@link DefaultHealthIndicatorRegistry}. * * @author Vedran Pavic + * @author Stephane Nicoll */ -public class DefaultHealthIndicatorRegistryTest { +public class DefaultHealthIndicatorRegistryTests { @Rule public ExpectedException thrown = ExpectedException.none(); @@ -43,9 +46,10 @@ public class DefaultHealthIndicatorRegistryTest { @Before public void setUp() { - given(this.one.health()).willReturn(new Health.Builder().up().build()); - given(this.two.health()).willReturn(new Health.Builder().unknown().build()); - + given(this.one.health()) + .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(); } @@ -60,9 +64,9 @@ public class DefaultHealthIndicatorRegistryTest { @Test public void registerAlreadyUsedName() { + this.registry.register("one", this.one); this.thrown.expect(IllegalStateException.class); this.thrown.expectMessage("HealthIndicator with name 'one' already registered"); - this.registry.register("one", this.one); this.registry.register("one", this.two); } @@ -77,7 +81,7 @@ public class DefaultHealthIndicatorRegistryTest { } @Test - public void unregisterNotKnown() { + public void unregisterUnknown() { this.registry.register("one", this.one); assertThat(this.registry.getAll()).hasSize(1); HealthIndicator two = this.registry.unregister("two"); @@ -85,4 +89,22 @@ public class DefaultHealthIndicatorRegistryTest { assertThat(this.registry.getAll()).hasSize(1); } + @Test + public void getAllIsASnapshot() { + this.registry.register("one", this.one); + Map 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 snapshot = this.registry.getAll(); + + this.thrown.expect(UnsupportedOperationException.class); + snapshot.clear(); + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java index f9c2ccb656..e2fd12f360 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java @@ -30,7 +30,6 @@ import static org.assertj.core.api.Assertions.entry; * @author Phillip Webb * @author Christian Dupuis * @author Andy Wilkinson - * @author Vedran Pavic */ public class HealthEndpointTests { @@ -41,8 +40,8 @@ public class HealthEndpointTests { .withDetail("first", "1").build()); healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP) .withDetail("second", "2").build()); - HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(), - createHealthIndicatorRegistry(healthIndicators)); + HealthEndpoint endpoint = new HealthEndpoint( + createHealthIndicator(healthIndicators)); Health health = endpoint.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getDetails()).containsOnlyKeys("up", "upAgain"); @@ -52,11 +51,10 @@ public class HealthEndpointTests { assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2")); } - private HealthIndicatorRegistry createHealthIndicatorRegistry( + private HealthIndicator createHealthIndicator( Map healthIndicators) { - HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); - healthIndicators.forEach(registry::register); - return registry; + return new CompositeHealthIndicator(new OrderedHealthAggregator(), + new DefaultHealthIndicatorRegistry(healthIndicators)); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java index 635c94269a..d8e90c59fb 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebIntegrationTests.java @@ -35,7 +35,6 @@ import org.springframework.test.web.reactive.server.WebTestClient; * exposed by Jersey, Spring MVC, and WebFlux. * * @author Andy Wilkinson - * @author Vedran Pavic */ @RunWith(WebEndpointRunners.class) public class HealthEndpointWebIntegrationTests { @@ -53,23 +52,48 @@ public class HealthEndpointWebIntegrationTests { @Test public void whenHealthIsDown503ResponseIsReturned() { - context.getBean("alphaHealthIndicator", TestHealthIndicator.class) - .setHealth(Health.down().build()); - client.get().uri("/actuator/health").exchange().expectStatus() - .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status") - .isEqualTo("DOWN").jsonPath("details.alpha.status").isEqualTo("DOWN") - .jsonPath("details.bravo.status").isEqualTo("UP"); + HealthIndicatorRegistry registry = context.getBean(HealthIndicatorRegistry.class); + registry.register("charlie", () -> Health.down().build()); + try { + client.get().uri("/actuator/health").exchange().expectStatus() + .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE).expectBody().jsonPath("status") + .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 public static class TestConfiguration { @Bean - public HealthEndpoint healthEndpoint( + public HealthIndicatorRegistry healthIndicatorFactory( Map healthIndicators) { - HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); - healthIndicators.forEach(registry::register); - return new HealthEndpoint(new OrderedHealthAggregator(), registry); + return new HealthIndicatorRegistryFactory() + .createHealthIndicatorRegistry(healthIndicators); + } + + @Bean + public HealthEndpoint healthEndpoint(HealthIndicatorRegistry registry) { + return new HealthEndpoint(new CompositeHealthIndicator( + new OrderedHealthAggregator(), registry)); } @Bean @@ -82,30 +106,13 @@ public class HealthEndpointWebIntegrationTests { } @Bean - public TestHealthIndicator alphaHealthIndicator() { - return new TestHealthIndicator(); + public HealthIndicator alphaHealthIndicator() { + return () -> Health.up().build(); } @Bean - public TestHealthIndicator bravoHealthIndicator() { - return new TestHealthIndicator(); - } - - } - - 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; + public HealthIndicator bravoHealthIndicator() { + return () -> Health.up().build(); } } diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 5a160174da..c46d089644 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -727,9 +727,9 @@ unauthenticated users. Health information is collected from all {sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] instances -registered with {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[`HealthIndicatorRegistry`]. -Spring Boot includes a number of auto-configured `HealthIndicators` and you can also write -your own. By default, the final system state is +registered with {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[ +`HealthIndicatorRegistry`]. Spring Boot includes a number of auto-configured +`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` 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 @@ -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`. 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 {sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] types, it is also possible for