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 e9b1fc7e5329..b059bd645138 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 @@ -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,10 +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.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.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -27,15 +30,27 @@ * Configuration for {@link HealthEndpoint}. * * @author Stephane Nicoll + * @author Vedran Pavic */ @Configuration 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(ApplicationContext applicationContext) { - return new HealthEndpoint(HealthIndicatorBeansComposite.get(applicationContext)); + public HealthEndpoint healthEndpoint() { + return new HealthEndpoint(this.healthAggregator, this.healthIndicatorRegistry); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java index 25b0a602c008..3da41f852ede 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java @@ -36,7 +36,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -107,11 +106,9 @@ static class ServletWebHealthConfiguration { @ConditionalOnEnabledEndpoint @ConditionalOnBean(HealthEndpoint.class) public HealthEndpointWebExtension healthEndpointWebExtension( - ApplicationContext applicationContext, + HealthEndpoint healthEndpoint, HealthWebEndpointResponseMapper responseMapper) { - return new HealthEndpointWebExtension( - HealthIndicatorBeansComposite.get(applicationContext), - responseMapper); + return new HealthEndpointWebExtension(healthEndpoint, responseMapper); } } 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 0247d393b855..5e27db3b3ea7 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 @@ -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,16 +16,23 @@ 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; import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.ReactiveHealthIndicator; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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. @@ -33,6 +40,7 @@ * @author Andy Wilkinson * @author Stephane Nicoll * @author Phillip Webb + * @author Vedran Pavic * @since 2.0.0 */ @Configuration @@ -61,4 +69,34 @@ public OrderedHealthAggregator healthAggregator() { return healthAggregator; } + @Bean + @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(); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorBeansComposite.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorBeansComposite.java deleted file mode 100644 index 502eb9ebaf62..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorBeansComposite.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.health; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.actuate.health.CompositeHealthIndicator; -import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory; -import org.springframework.boot.actuate.health.HealthAggregator; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.OrderedHealthAggregator; -import org.springframework.boot.actuate.health.ReactiveHealthIndicator; -import org.springframework.context.ApplicationContext; -import org.springframework.util.ClassUtils; - -/** - * Creates a {@link CompositeHealthIndicator} from beans in the - * {@link ApplicationContext}. - * - * @author Phillip Webb - */ -final class HealthIndicatorBeansComposite { - - private HealthIndicatorBeansComposite() { - } - - public static HealthIndicator get(ApplicationContext applicationContext) { - HealthAggregator healthAggregator = getHealthAggregator(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); - } - CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory(); - return factory.createHealthIndicator(healthAggregator, indicators); - } - - private static HealthAggregator getHealthAggregator( - ApplicationContext applicationContext) { - try { - return applicationContext.getBean(HealthAggregator.class); - } - catch (NoSuchBeanDefinitionException ex) { - return new OrderedHealthAggregator(); - } - } - - 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 31acdc80344c..085fabe106e7 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,8 +34,9 @@ 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.HealthIndicator; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -109,7 +110,8 @@ public TestEndpointWebExtension testEndpointWebExtension() { @Bean public HealthEndpoint healthEndpoint() { - return new HealthEndpoint(mock(HealthIndicator.class)); + return new HealthEndpoint(mock(HealthAggregator.class), + mock(HealthIndicatorRegistry.class)); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java index 111366da84f0..d1a486a5f111 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java @@ -32,6 +32,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -87,6 +88,7 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests { WebClientCustomizerConfig.class, WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java index 94e6afde968c..16c4bc607223 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java @@ -24,6 +24,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -270,7 +271,8 @@ public void healthEndpointInvokerShouldBeCloudFoundryWebExtension() { "vcap.application.application_id:my-app-id", "vcap.application.cf_api:http://my-cloud-controller.com") .withConfiguration( - AutoConfigurations.of(HealthEndpointAutoConfiguration.class)) + AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class)) .run((context) -> { Collection endpoints = context .getBean("cloudFoundryWebEndpointServletHandlerMapping", 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 502e5484fb9e..28036a283dd0 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,9 +23,10 @@ import org.junit.Test; -import org.springframework.boot.actuate.health.CompositeHealthIndicator; +import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry; 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.OrderedHealthAggregator; import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator; import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator; @@ -72,8 +73,9 @@ static class TestConfiguration { @Bean public HealthEndpoint endpoint(Map healthIndicators) { - return new HealthEndpoint(new CompositeHealthIndicator( - new OrderedHealthAggregator(), healthIndicators)); + HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); + healthIndicators.forEach(registry::register); + return new HealthEndpoint(new OrderedHealthAggregator(), registry); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java index 2600b6fb9796..bae97ee25cf7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java @@ -46,7 +46,8 @@ public class HealthEndpointAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration( - AutoConfigurations.of(HealthEndpointAutoConfiguration.class)); + AutoConfigurations.of(HealthIndicatorAutoConfiguration.class, + HealthEndpointAutoConfiguration.class)); @Test public void healthEndpointShowDetailsDefault() { 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 76ffe93a8783..700f22962c6d 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 @@ -28,6 +28,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; @@ -47,7 +48,7 @@ public class JmxEndpointIntegrationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class, - HttpTraceAutoConfiguration.class)) + HttpTraceAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)) .withConfiguration( AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java index 27788c8ad147..472af0bdafb4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java @@ -28,6 +28,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; @@ -73,7 +74,8 @@ public class WebMvcEndpointExposureIntegrationTests { ServletManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class, - HttpTraceAutoConfiguration.class)) + HttpTraceAutoConfiguration.class, + HealthIndicatorAutoConfiguration.class)) .withConfiguration( AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)) .withUserConfiguration(CustomMvcEndpoint.class, 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 new file mode 100644 index 000000000000..55f528daa5e5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java @@ -0,0 +1,68 @@ +/* + * 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.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * Default implementation of {@link HealthIndicatorRegistry}. + * + * @author Vedran Pavic + * @since 2.1.0 + */ +public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry { + + private final Map healthIndicators = new HashMap<>(); + + @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) { + throw new IllegalStateException( + "HealthIndicator with name '" + name + "' already registered"); + } + this.healthIndicators.put(name, healthIndicator); + } + } + + @Override + public HealthIndicator unregister(String name) { + synchronized (this.healthIndicators) { + return this.healthIndicators.remove(name); + } + } + + @Override + public HealthIndicator get(String name) { + synchronized (this.healthIndicators) { + return this.healthIndicators.get(name); + } + } + + @Override + public Map getAll() { + synchronized (this.healthIndicators) { + return Collections.unmodifiableMap(new HashMap<>(this.healthIndicators)); + } + } + +} 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 ce157a3404d9..59390b6a8bc5 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,25 +26,35 @@ * @author Dave Syer * @author Christian Dupuis * @author Andy Wilkinson + * @author Vedran Pavic * @since 2.0.0 */ @Endpoint(id = "health") public class HealthEndpoint { - private final HealthIndicator healthIndicator; + private final HealthAggregator healthAggregator; + + private final HealthIndicatorRegistry healthIndicatorRegistry; /** * Create a new {@link HealthEndpoint} instance. - * @param healthIndicator the health indicator + * @param healthAggregator the health aggregator + * @param healthIndicatorRegistry the health indicator registry */ - public HealthEndpoint(HealthIndicator healthIndicator) { - Assert.notNull(healthIndicator, "HealthIndicator must not be null"); - this.healthIndicator = healthIndicator; + 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; } @ReadOperation public Health health() { - return this.healthIndicator.health(); + CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory(); + CompositeHealthIndicator healthIndicator = factory.createHealthIndicator( + this.healthAggregator, this.healthIndicatorRegistry.getAll()); + return healthIndicator.health(); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java index 4eed4b4a8cc2..420db8a56b7f 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java @@ -35,11 +35,11 @@ @EndpointWebExtension(endpoint = HealthEndpoint.class) public class HealthEndpointWebExtension { - private final HealthIndicator delegate; + private final HealthEndpoint delegate; private final HealthWebEndpointResponseMapper responseMapper; - public HealthEndpointWebExtension(HealthIndicator delegate, + public HealthEndpointWebExtension(HealthEndpoint delegate, HealthWebEndpointResponseMapper responseMapper) { this.delegate = delegate; this.responseMapper = responseMapper; 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 new file mode 100644 index 000000000000..b499771bcbd7 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistry.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; + +/** + * A registry of {@link HealthIndicator}s. + *

+ * Implementations must be thread-safe. + * + * @author Andy Wilkinson + * @author Vedran Pavic + * @since 2.1.0 + */ +public interface HealthIndicatorRegistry { + + /** + * 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. + */ + void register(String name, HealthIndicator healthIndicator); + + /** + * 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}. + */ + 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}. + */ + 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. + * @return the snapshot of registered health indicators + */ + Map getAll(); + +} 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/DefaultHealthIndicatorRegistryTest.java new file mode 100644 index 000000000000..c24848c24e57 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTest.java @@ -0,0 +1,88 @@ +/* + * 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 org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link DefaultHealthIndicatorRegistry}. + * + * @author Vedran Pavic + */ +public class DefaultHealthIndicatorRegistryTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private HealthIndicator one = mock(HealthIndicator.class); + + private HealthIndicator two = mock(HealthIndicator.class); + + private DefaultHealthIndicatorRegistry registry; + + @Before + public void setUp() { + given(this.one.health()).willReturn(new Health.Builder().up().build()); + given(this.two.health()).willReturn(new Health.Builder().unknown().build()); + + this.registry = new DefaultHealthIndicatorRegistry(); + } + + @Test + public void register() { + this.registry.register("one", this.one); + this.registry.register("two", this.two); + assertThat(this.registry.getAll()).hasSize(2); + assertThat(this.registry.get("one")).isSameAs(this.one); + assertThat(this.registry.get("two")).isSameAs(this.two); + } + + @Test + public void registerAlreadyUsedName() { + 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); + } + + @Test + public void unregister() { + this.registry.register("one", this.one); + this.registry.register("two", this.two); + assertThat(this.registry.getAll()).hasSize(2); + HealthIndicator two = this.registry.unregister("two"); + assertThat(two).isSameAs(this.two); + assertThat(this.registry.getAll()).hasSize(1); + } + + @Test + public void unregisterNotKnown() { + this.registry.register("one", this.one); + assertThat(this.registry.getAll()).hasSize(1); + HealthIndicator two = this.registry.unregister("two"); + assertThat(two).isNull(); + assertThat(this.registry.getAll()).hasSize(1); + } + +} 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 66da11d9ae49..f9c2ccb65607 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 @@ -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 @@ * @author Phillip Webb * @author Christian Dupuis * @author Andy Wilkinson + * @author Vedran Pavic */ public class HealthEndpointTests { @@ -40,8 +41,8 @@ public void statusAndFullDetailsAreExposed() { .withDetail("first", "1").build()); healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP) .withDetail("second", "2").build()); - HealthEndpoint endpoint = new HealthEndpoint( - createHealthIndicator(healthIndicators)); + HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(), + createHealthIndicatorRegistry(healthIndicators)); Health health = endpoint.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getDetails()).containsOnlyKeys("up", "upAgain"); @@ -51,10 +52,11 @@ public void statusAndFullDetailsAreExposed() { assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2")); } - private HealthIndicator createHealthIndicator( + private HealthIndicatorRegistry createHealthIndicatorRegistry( Map healthIndicators) { - return new CompositeHealthIndicatorFactory() - .createHealthIndicator(new OrderedHealthAggregator(), healthIndicators); + HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); + healthIndicators.forEach(registry::register); + return registry; } } 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 6f06c212c3b6..635c94269a7c 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,6 +35,7 @@ * exposed by Jersey, Spring MVC, and WebFlux. * * @author Andy Wilkinson + * @author Vedran Pavic */ @RunWith(WebEndpointRunners.class) public class HealthEndpointWebIntegrationTests { @@ -66,17 +67,15 @@ public static class TestConfiguration { @Bean public HealthEndpoint healthEndpoint( Map healthIndicators) { - return new HealthEndpoint( - new CompositeHealthIndicatorFactory().createHealthIndicator( - new OrderedHealthAggregator(), healthIndicators)); + HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); + healthIndicators.forEach(registry::register); + return new HealthEndpoint(new OrderedHealthAggregator(), registry); } @Bean public HealthEndpointWebExtension healthWebEndpointExtension( - Map healthIndicators) { - return new HealthEndpointWebExtension( - new CompositeHealthIndicatorFactory().createHealthIndicator( - new OrderedHealthAggregator(), healthIndicators), + HealthEndpoint healthEndpoint) { + return new HealthEndpointWebExtension(healthEndpoint, new HealthWebEndpointResponseMapper(new HealthStatusHttpMapper(), ShowDetails.ALWAYS, new HashSet<>(Arrays.asList("ACTUATOR")))); 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 b2097a5e4e6f..5a160174da65 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 @@ -726,13 +726,14 @@ configuration must permit access to the health endpoint for both authenticated a unauthenticated users. Health information is collected from all -{sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] beans -defined in your `ApplicationContext`. 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 `HealthAggregator`, an `UNKNOWN` status is used. +{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 +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 +`HealthAggregator`, an `UNKNOWN` status is used. @@ -818,6 +819,9 @@ NOTE: The identifier for a given `HealthIndicator` is the name of the bean witho `HealthIndicator` suffix, if it exists. In the preceding example, the health information 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`]. + In addition to Spring Boot's predefined {sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] types, it is also possible for `Health` to return a custom `Status` that represents a new system state. In such cases, a