Skip to content

Commit 42b92f8

Browse files
committed
Introduce HealthIndicatorRegistry
This commit introduces HealthIndicatorRegistry which handles registration and invocation of HealthIndicator instances. Registering new HealthIndicator instances is now possible in runtime. The default implementation offers the option to run the registered health indicators in parallel.
1 parent 0f2c25c commit 42b92f8

File tree

11 files changed

+457
-40
lines changed

11 files changed

+457
-40
lines changed

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
1919
import java.util.ArrayList;
2020
import java.util.Collection;
2121
import java.util.Collections;
22-
import java.util.HashMap;
2322
import java.util.LinkedHashMap;
2423
import java.util.List;
2524
import java.util.Map;
@@ -46,8 +45,9 @@
4645
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
4746
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
4847
import org.springframework.boot.actuate.endpoint.TraceEndpoint;
48+
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;
4949
import org.springframework.boot.actuate.health.HealthAggregator;
50-
import org.springframework.boot.actuate.health.HealthIndicator;
50+
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
5151
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
5252
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
5353
import org.springframework.boot.actuate.trace.TraceRepository;
@@ -81,6 +81,7 @@
8181
* @author Christian Dupuis
8282
* @author Stephane Nicoll
8383
* @author Eddú Meléndez
84+
* @author Vedran Pavic
8485
*/
8586
@Configuration
8687
@AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class })
@@ -94,7 +95,7 @@ public class EndpointAutoConfiguration {
9495
private HealthAggregator healthAggregator = new OrderedHealthAggregator();
9596

9697
@Autowired(required = false)
97-
private Map<String, HealthIndicator> healthIndicators = new HashMap<String, HealthIndicator>();
98+
private HealthIndicatorRegistry healthIndicatorRegistry = new DefaultHealthIndicatorRegistry();
9899

99100
@Autowired(required = false)
100101
private Collection<PublicMetrics> publicMetrics;
@@ -111,7 +112,7 @@ public EnvironmentEndpoint environmentEndpoint() {
111112
@Bean
112113
@ConditionalOnMissingBean
113114
public HealthEndpoint healthEndpoint() {
114-
return new HealthEndpoint(this.healthAggregator, this.healthIndicators);
115+
return new HealthEndpoint(this.healthAggregator, this.healthIndicatorRegistry);
115116
}
116117

117118
@Bean

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

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,18 +27,23 @@
2727
import org.elasticsearch.client.Client;
2828

2929
import org.springframework.amqp.rabbit.core.RabbitTemplate;
30+
import org.springframework.beans.BeansException;
3031
import org.springframework.beans.factory.InitializingBean;
3132
import org.springframework.beans.factory.annotation.Autowired;
33+
import org.springframework.beans.factory.config.BeanPostProcessor;
3234
import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
3335
import org.springframework.boot.actuate.health.CassandraHealthIndicator;
3436
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
3537
import org.springframework.boot.actuate.health.DataSourceHealthIndicator;
38+
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;
39+
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistryProperties;
3640
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator;
3741
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties;
3842
import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator;
3943
import org.springframework.boot.actuate.health.ElasticsearchHealthIndicatorProperties;
4044
import org.springframework.boot.actuate.health.HealthAggregator;
4145
import org.springframework.boot.actuate.health.HealthIndicator;
46+
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
4247
import org.springframework.boot.actuate.health.JmsHealthIndicator;
4348
import org.springframework.boot.actuate.health.MailHealthIndicator;
4449
import org.springframework.boot.actuate.health.MongoHealthIndicator;
@@ -84,6 +89,7 @@
8489
* @author Stephane Nicoll
8590
* @author Phillip Webb
8691
* @author Tommy Ludwig
92+
* @author Vedran Pavic
8793
* @since 1.1.0
8894
*/
8995
@Configuration
@@ -100,6 +106,14 @@ public class HealthIndicatorAutoConfiguration {
100106
@Autowired
101107
private HealthIndicatorAutoConfigurationProperties configurationProperties = new HealthIndicatorAutoConfigurationProperties();
102108

109+
@Autowired
110+
private HealthIndicatorRegistry healthIndicatorRegistry;
111+
112+
@Bean
113+
public HealthIndicatorRegistryPostProcessor healthIndicatorRegistryPostProcessor() {
114+
return new HealthIndicatorRegistryPostProcessor(this.healthIndicatorRegistry);
115+
}
116+
103117
@Bean
104118
@ConditionalOnMissingBean(HealthAggregator.class)
105119
public OrderedHealthAggregator healthAggregator() {
@@ -116,6 +130,21 @@ public ApplicationHealthIndicator applicationHealthIndicator() {
116130
return new ApplicationHealthIndicator();
117131
}
118132

133+
@Configuration
134+
@ConditionalOnMissingBean(HealthIndicatorRegistry.class)
135+
@EnableConfigurationProperties(DefaultHealthIndicatorRegistryProperties.class)
136+
public static class HealthIndicatorRegistryAutoConfiguration {
137+
138+
@Autowired
139+
private DefaultHealthIndicatorRegistryProperties properties;
140+
141+
@Bean
142+
public HealthIndicatorRegistry healthIndicatorRegistry() {
143+
return new DefaultHealthIndicatorRegistry(this.properties);
144+
}
145+
146+
}
147+
119148
/**
120149
* Base class for configurations that can combine source beans using a
121150
* {@link CompositeHealthIndicator}.
@@ -363,4 +392,51 @@ protected ElasticsearchHealthIndicator createHealthIndicator(Client client) {
363392

364393
}
365394

395+
/**
396+
* A {@link BeanPostProcessor} that registers {@link HealthIndicator} beans with a
397+
* {@link HealthIndicatorRegistry}.
398+
*/
399+
private static class HealthIndicatorRegistryPostProcessor implements BeanPostProcessor {
400+
401+
private HealthIndicatorRegistry healthIndicatorRegistry;
402+
403+
HealthIndicatorRegistryPostProcessor(HealthIndicatorRegistry healthIndicatorRegistry) {
404+
this.healthIndicatorRegistry = healthIndicatorRegistry;
405+
}
406+
407+
@Override
408+
public Object postProcessBeforeInitialization(Object bean, String beanName)
409+
throws BeansException {
410+
return bean;
411+
}
412+
413+
@Override
414+
public Object postProcessAfterInitialization(Object bean, String beanName)
415+
throws BeansException {
416+
if (bean instanceof HealthIndicator) {
417+
postProcessHealthIndicator((HealthIndicator) bean, beanName);
418+
}
419+
return bean;
420+
}
421+
422+
private void postProcessHealthIndicator(
423+
HealthIndicator healthIndicator, String beanName) {
424+
this.healthIndicatorRegistry.register(getKey(beanName), healthIndicator);
425+
}
426+
427+
/**
428+
* Turns the bean name into a key that can be used in the map of health information.
429+
* @param name the bean name
430+
* @return the key
431+
*/
432+
private String getKey(String name) {
433+
int index = name.toLowerCase().indexOf("healthindicator");
434+
if (index > 0) {
435+
return name.substring(0, index);
436+
}
437+
return name;
438+
}
439+
440+
}
441+
366442
}

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

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,10 +18,10 @@
1818

1919
import java.util.Map;
2020

21-
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
2221
import org.springframework.boot.actuate.health.Health;
2322
import org.springframework.boot.actuate.health.HealthAggregator;
2423
import org.springframework.boot.actuate.health.HealthIndicator;
24+
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
2525
import org.springframework.boot.context.properties.ConfigurationProperties;
2626
import org.springframework.util.Assert;
2727

@@ -31,11 +31,13 @@
3131
* @author Dave Syer
3232
* @author Christian Dupuis
3333
* @author Andy Wilkinson
34+
* @author Vedran Pavic
3435
*/
3536
@ConfigurationProperties(prefix = "endpoints.health", ignoreUnknownFields = true)
3637
public class HealthEndpoint extends AbstractEndpoint<Health> {
3738

38-
private final HealthIndicator healthIndicator;
39+
private final HealthAggregator healthAggregator;
40+
private final HealthIndicatorRegistry healthIndicatorRegistry;
3941

4042
/**
4143
* Time to live for cached result, in milliseconds.
@@ -45,19 +47,16 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
4547
/**
4648
* Create a new {@link HealthIndicator} instance.
4749
* @param healthAggregator the health aggregator
48-
* @param healthIndicators the health indicators
50+
* @param healthIndicatorRegistry the health indicator registry
4951
*/
5052
public HealthEndpoint(HealthAggregator healthAggregator,
51-
Map<String, HealthIndicator> healthIndicators) {
53+
HealthIndicatorRegistry healthIndicatorRegistry) {
5254
super("health", false);
5355
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
54-
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
55-
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
56-
healthAggregator);
57-
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
58-
healthIndicator.addHealthIndicator(getKey(entry.getKey()), entry.getValue());
59-
}
60-
this.healthIndicator = healthIndicator;
56+
Assert.notNull(healthIndicatorRegistry,
57+
"healthIndicatorRegistry must not be null");
58+
this.healthAggregator = healthAggregator;
59+
this.healthIndicatorRegistry = healthIndicatorRegistry;
6160
}
6261

6362
/**
@@ -78,19 +77,8 @@ public void setTimeToLive(long ttl) {
7877
*/
7978
@Override
8079
public Health invoke() {
81-
return this.healthIndicator.health();
80+
Map<String, Health> healths = this.healthIndicatorRegistry.runHealthIndicators();
81+
return this.healthAggregator.aggregate(healths);
8282
}
8383

84-
/**
85-
* Turns the bean name into a key that can be used in the map of health information.
86-
* @param name the bean name
87-
* @return the key
88-
*/
89-
private String getKey(String name) {
90-
int index = name.toLowerCase().indexOf("healthindicator");
91-
if (index > 0) {
92-
return name.substring(0, index);
93-
}
94-
return name;
95-
}
9684
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2012-2016 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+
import java.util.Set;
23+
import java.util.concurrent.Callable;
24+
import java.util.concurrent.ExecutorService;
25+
import java.util.concurrent.Executors;
26+
import java.util.concurrent.Future;
27+
28+
import org.springframework.util.Assert;
29+
30+
/**
31+
* Default implementation of {@link HealthIndicatorRegistry}.
32+
*
33+
* @author Vedran Pavic
34+
*/
35+
public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry {
36+
37+
private final Map<String, HealthIndicator> healthIndicators =
38+
new HashMap<String, HealthIndicator>();
39+
40+
private DefaultHealthIndicatorRegistryProperties properties;
41+
42+
private ExecutorService executor;
43+
44+
/**
45+
* Create a {@link DefaultHealthIndicatorRegistry} instance.
46+
* @param properties the configuration properties
47+
*/
48+
public DefaultHealthIndicatorRegistry(DefaultHealthIndicatorRegistryProperties properties) {
49+
Assert.notNull(properties, "Properties must not be null");
50+
this.properties = properties;
51+
this.executor = Executors.newFixedThreadPool(this.properties.getThreadCount());
52+
}
53+
54+
/**
55+
* Create a {@link DefaultHealthIndicatorRegistry} instance.
56+
*/
57+
public DefaultHealthIndicatorRegistry() {
58+
this(new DefaultHealthIndicatorRegistryProperties());
59+
}
60+
61+
@Override
62+
public void register(String name, HealthIndicator healthIndicator) {
63+
this.healthIndicators.put(name, healthIndicator);
64+
}
65+
66+
@Override
67+
public void unregister(String name) {
68+
this.healthIndicators.remove(name);
69+
}
70+
71+
@Override
72+
public Set<String> getRegisteredNames() {
73+
return Collections.unmodifiableSet(this.healthIndicators.keySet());
74+
}
75+
76+
@Override
77+
public Health runHealthIndicator(String name) {
78+
HealthIndicator healthIndicator = this.healthIndicators.get(name);
79+
Assert.notNull(healthIndicator, "HealthIndicator " + name + " does not exist");
80+
return healthIndicator.health();
81+
}
82+
83+
@Override
84+
public Map<String, Health> runHealthIndicators() {
85+
Map<String, Health> healths =
86+
new HashMap<String, Health>(this.healthIndicators.size());
87+
if (this.properties.isRunInParallel()) {
88+
Assert.notNull(this.executor, "Executor must not be null");
89+
Map<String, Future<Health>> futures =
90+
new HashMap<String, Future<Health>>(this.healthIndicators.size());
91+
for (final Map.Entry<String, HealthIndicator> entry : this.healthIndicators.entrySet()) {
92+
Future<Health> future = this.executor.submit(new Callable<Health>() {
93+
@Override
94+
public Health call() throws Exception {
95+
return entry.getValue().health();
96+
}
97+
});
98+
futures.put(entry.getKey(), future);
99+
}
100+
for (Map.Entry<String, Future<Health>> entry : futures.entrySet()) {
101+
try {
102+
healths.put(entry.getKey(), entry.getValue().get());
103+
}
104+
catch (Exception e) {
105+
e.printStackTrace();
106+
}
107+
}
108+
}
109+
else {
110+
for (Map.Entry<String, HealthIndicator> entry : this.healthIndicators.entrySet()) {
111+
healths.put(entry.getKey(), entry.getValue().health());
112+
}
113+
}
114+
return healths;
115+
}
116+
117+
}

0 commit comments

Comments
 (0)