Skip to content

Commit 2ca7acb

Browse files
committed
Introduce HealthIndicatorRegistry
This commit introduces HealthIndicatorRegistry which handles registration of HealthIndicator instances. Registering new HealthIndicator instances is now possible in runtime.
1 parent 43ff84e commit 2ca7acb

File tree

15 files changed

+336
-73
lines changed

15 files changed

+336
-73
lines changed

spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java

+10-41
Original file line numberDiff line numberDiff line change
@@ -16,83 +16,52 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.health;
1818

19-
import java.util.LinkedHashMap;
20-
import java.util.Map;
21-
2219
import org.springframework.beans.factory.ObjectProvider;
2320
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
24-
import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory;
2521
import org.springframework.boot.actuate.health.HealthAggregator;
2622
import org.springframework.boot.actuate.health.HealthEndpoint;
27-
import org.springframework.boot.actuate.health.HealthIndicator;
23+
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
2824
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
29-
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
3025
import org.springframework.boot.actuate.health.StatusEndpoint;
3126
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3227
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
33-
import org.springframework.context.ApplicationContext;
3428
import org.springframework.context.annotation.Bean;
3529
import org.springframework.context.annotation.Configuration;
36-
import org.springframework.util.ClassUtils;
3730

3831
/**
3932
* {@link EnableAutoConfiguration Auto-configuration} for {@link HealthEndpoint}.
4033
*
4134
* @author Andy Wilkinson
4235
* @author Stephane Nicoll
4336
* @author Phillip Webb
37+
* @author Vedran Pavic
4438
* @since 2.0.0
4539
*/
4640
@Configuration
4741
public class HealthEndpointAutoConfiguration {
4842

49-
private final HealthIndicator healthIndicator;
43+
private final HealthAggregator healthAggregator;
5044

51-
public HealthEndpointAutoConfiguration(ApplicationContext applicationContext,
52-
ObjectProvider<HealthAggregator> healthAggregator) {
53-
this.healthIndicator = getHealthIndicator(applicationContext,
54-
healthAggregator.getIfAvailable(OrderedHealthAggregator::new));
55-
}
45+
private final HealthIndicatorRegistry healthIndicatorRegistry;
5646

57-
private HealthIndicator getHealthIndicator(ApplicationContext applicationContext,
58-
HealthAggregator healthAggregator) {
59-
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
60-
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
61-
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
62-
new ReactiveHealthIndicators().get(applicationContext)
63-
.forEach(indicators::putIfAbsent);
64-
}
65-
CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
66-
return factory.createHealthIndicator(healthAggregator, indicators);
47+
public HealthEndpointAutoConfiguration(ObjectProvider<HealthAggregator> healthAggregator,
48+
ObjectProvider<HealthIndicatorRegistry> healthIndicatorRegistry) {
49+
this.healthAggregator = healthAggregator.getIfAvailable(OrderedHealthAggregator::new);
50+
this.healthIndicatorRegistry = healthIndicatorRegistry.getObject();
6751
}
6852

6953
@Bean
7054
@ConditionalOnMissingBean
7155
@ConditionalOnEnabledEndpoint
7256
public HealthEndpoint healthEndpoint() {
73-
return new HealthEndpoint(this.healthIndicator);
57+
return new HealthEndpoint(this.healthAggregator, this.healthIndicatorRegistry);
7458
}
7559

7660
@Bean
7761
@ConditionalOnMissingBean
7862
@ConditionalOnEnabledEndpoint
7963
public StatusEndpoint statusEndpoint() {
80-
return new StatusEndpoint(this.healthIndicator);
81-
}
82-
83-
private static class ReactiveHealthIndicators {
84-
85-
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
86-
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
87-
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
88-
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
89-
return indicators;
90-
}
91-
92-
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
93-
return () -> indicator.health().block();
94-
}
95-
64+
return new StatusEndpoint(this.healthAggregator, this.healthIndicatorRegistry);
9665
}
9766

9867
}

spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java

+38
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,31 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.health;
1818

19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
21+
1922
import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
23+
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;
2024
import org.springframework.boot.actuate.health.HealthAggregator;
2125
import org.springframework.boot.actuate.health.HealthIndicator;
26+
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
2227
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
2328
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
2429
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2530
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2631
import org.springframework.boot.context.properties.EnableConfigurationProperties;
32+
import org.springframework.context.ApplicationContext;
2733
import org.springframework.context.annotation.Bean;
2834
import org.springframework.context.annotation.Configuration;
35+
import org.springframework.util.ClassUtils;
2936

3037
/**
3138
* {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s.
3239
*
3340
* @author Andy Wilkinson
3441
* @author Stephane Nicoll
3542
* @author Phillip Webb
43+
* @author Vedran Pavic
3644
* @since 2.0.0
3745
*/
3846
@Configuration
@@ -61,4 +69,34 @@ public OrderedHealthAggregator healthAggregator() {
6169
return healthAggregator;
6270
}
6371

72+
@Bean
73+
@ConditionalOnMissingBean(HealthIndicatorRegistry.class)
74+
public HealthIndicatorRegistry healthIndicatorRegistry(
75+
ApplicationContext applicationContext) {
76+
HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry();
77+
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
78+
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
79+
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
80+
new ReactiveHealthIndicators().get(applicationContext)
81+
.forEach(indicators::putIfAbsent);
82+
}
83+
indicators.forEach(registry::register);
84+
return registry;
85+
}
86+
87+
private static class ReactiveHealthIndicators {
88+
89+
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
90+
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
91+
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
92+
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
93+
return indicators;
94+
}
95+
96+
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
97+
return () -> indicator.health().block();
98+
}
99+
100+
}
101+
64102
}

spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public class HealthEndpointAutoConfigurationTests {
4646

4747
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
4848
.withConfiguration(
49-
AutoConfigurations.of(HealthEndpointAutoConfiguration.class));
49+
AutoConfigurations.of(HealthIndicatorAutoConfiguration.class,
50+
HealthEndpointAutoConfiguration.class));
5051

5152
@Test
5253
public void healthEndpointAdaptReactiveHealthIndicator() {
@@ -118,4 +119,5 @@ public ReactiveHealthIndicator reactiveHealthIndicator() {
118119
}
119120

120121
}
122+
121123
}

spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JmxEndpointIntegrationTests.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
3030
import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration;
31+
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
3132
import org.springframework.boot.autoconfigure.AutoConfigurations;
3233
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
3334
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -45,7 +46,8 @@ public class JmxEndpointIntegrationTests {
4546

4647
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
4748
.withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class,
48-
EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class))
49+
EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class,
50+
HealthIndicatorAutoConfiguration.class))
4951
.withConfiguration(
5052
AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL));
5153

spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointExposureIntegrationTests.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.junit.Test;
2020

2121
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
22+
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
2223
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
2324
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
2425
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -56,7 +57,8 @@ public class WebMvcEndpointExposureIntegrationTests {
5657
ManagementContextAutoConfiguration.class,
5758
ServletManagementContextAutoConfiguration.class,
5859
ManagementContextAutoConfiguration.class,
59-
ServletManagementContextAutoConfiguration.class))
60+
ServletManagementContextAutoConfiguration.class,
61+
HealthIndicatorAutoConfiguration.class))
6062
.withConfiguration(
6163
AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL));
6264

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.health;
18+
19+
import java.util.Collections;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* Default implementation of {@link HealthIndicatorRegistry}.
27+
*
28+
* @author Vedran Pavic
29+
* @since 2.0.0
30+
*/
31+
public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry {
32+
33+
private final Map<String, HealthIndicator> healthIndicators = new HashMap<>();
34+
35+
@Override
36+
public void register(String name, HealthIndicator healthIndicator) {
37+
Assert.notNull(healthIndicator, "HealthIndicator must not be null");
38+
synchronized (this.healthIndicators) {
39+
if (this.healthIndicators.get(name) != null) {
40+
throw new IllegalStateException(
41+
"HealthIndicator with name '" + name + "' already registered");
42+
}
43+
this.healthIndicators.put(name, healthIndicator);
44+
}
45+
}
46+
47+
@Override
48+
public HealthIndicator unregister(String name) {
49+
synchronized (this.healthIndicators) {
50+
return this.healthIndicators.remove(name);
51+
}
52+
}
53+
54+
@Override
55+
public HealthIndicator get(String name) {
56+
synchronized (this.healthIndicators) {
57+
return this.healthIndicators.get(name);
58+
}
59+
}
60+
61+
@Override
62+
public Map<String, HealthIndicator> getAll() {
63+
synchronized (this.healthIndicators) {
64+
return Collections.unmodifiableMap(new HashMap<>(this.healthIndicators));
65+
}
66+
}
67+
68+
}

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,33 @@
2525
* @author Dave Syer
2626
* @author Christian Dupuis
2727
* @author Andy Wilkinson
28+
* @author Vedran Pavic
2829
* @since 2.0.0
2930
*/
3031
@Endpoint(id = "health")
3132
public class HealthEndpoint {
3233

33-
private final HealthIndicator healthIndicator;
34+
private final HealthAggregator healthAggregator;
35+
36+
private final HealthIndicatorRegistry healthIndicatorRegistry;
3437

3538
/**
3639
* Create a new {@link HealthEndpoint} instance.
37-
* @param healthIndicator the health indicator
40+
* @param healthAggregator the health aggregator
41+
* @param healthIndicatorRegistry the health indicator registry
3842
*/
39-
public HealthEndpoint(HealthIndicator healthIndicator) {
40-
this.healthIndicator = healthIndicator;
43+
public HealthEndpoint(HealthAggregator healthAggregator,
44+
HealthIndicatorRegistry healthIndicatorRegistry) {
45+
this.healthAggregator = healthAggregator;
46+
this.healthIndicatorRegistry = healthIndicatorRegistry;
4147
}
4248

4349
@ReadOperation
4450
public Health health() {
45-
return this.healthIndicator.health();
51+
CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
52+
CompositeHealthIndicator healthIndicator = factory.createHealthIndicator(
53+
this.healthAggregator, this.healthIndicatorRegistry.getAll());
54+
return healthIndicator.health();
4655
}
4756

4857
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.health;
18+
19+
import java.util.Map;
20+
21+
/**
22+
* A registry of {@link HealthIndicator}s.
23+
* <p>
24+
* Implementations <strong>must</strong> be thread-safe.
25+
*
26+
* @author Andy Wilkinson
27+
* @author Vedran Pavic
28+
* @since 2.0.0
29+
*/
30+
public interface HealthIndicatorRegistry {
31+
32+
/**
33+
* Registers the given {@code healthIndicator}, associating it with the given
34+
* {@code name}.
35+
* @param name the name of the indicator
36+
* @param healthIndicator the indicator
37+
* @throws IllegalStateException if an indicator with the given {@code name} is
38+
* already registered.
39+
*/
40+
void register(String name, HealthIndicator healthIndicator);
41+
42+
/**
43+
* Unregisters the {@code HealthIndicator} previously registered with the given
44+
* {@code name}.
45+
* @param name the name of the indicator
46+
* @return the unregistered indicator, or {@code null} if no indicator was found in
47+
* the registry for the given {@code name}.
48+
*/
49+
HealthIndicator unregister(String name);
50+
51+
/**
52+
* Returns the health indicator registered with the given {@code name}.
53+
* @param name the name of the indicator
54+
* @return the health indicator, or {@code null} if no indicator was registered with
55+
* the given {@code name}.
56+
*/
57+
HealthIndicator get(String name);
58+
59+
/**
60+
* Returns a snapshot of the registered health indicators and their names. The
61+
* contents of the map do not reflect subsequent changes to the registry.
62+
* @return the snapshot of registered health indicators
63+
*/
64+
Map<String, HealthIndicator> getAll();
65+
66+
}

0 commit comments

Comments
 (0)