Introduce HealthIndicatorRegistry

This commit introduces HealthIndicatorRegistry which handles
registration of HealthIndicator instances. Registering new
HealthIndicator instances is now possible in runtime.

See gh-4965
This commit is contained in:
Vedran Pavic
2016-01-18 21:17:55 +01:00
committed by Stephane Nicoll
parent ffdcdc0dc6
commit d829d522be
19 changed files with 346 additions and 127 deletions

View File

@@ -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<String, HealthIndicator> 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<String, HealthIndicator> getAll() {
synchronized (this.healthIndicators) {
return Collections.unmodifiableMap(new HashMap<>(this.healthIndicators));
}
}
}

View File

@@ -26,25 +26,35 @@ 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 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();
}
}

View File

@@ -35,11 +35,11 @@ import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExten
@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;

View File

@@ -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.
* <p>
* Implementations <strong>must</strong> 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<String, HealthIndicator> getAll();
}

View File

@@ -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);
}
}

View File

@@ -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.entry;
* @author Phillip Webb
* @author Christian Dupuis
* @author Andy Wilkinson
* @author Vedran Pavic
*/
public class HealthEndpointTests {
@@ -40,8 +41,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(
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 class HealthEndpointTests {
assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2"));
}
private HealthIndicator createHealthIndicator(
private HealthIndicatorRegistry createHealthIndicatorRegistry(
Map<String, HealthIndicator> healthIndicators) {
return new CompositeHealthIndicatorFactory()
.createHealthIndicator(new OrderedHealthAggregator(), healthIndicators);
HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry();
healthIndicators.forEach(registry::register);
return registry;
}
}

View File

@@ -35,6 +35,7 @@ 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 {
@@ -66,17 +67,15 @@ public class HealthEndpointWebIntegrationTests {
@Bean
public HealthEndpoint healthEndpoint(
Map<String, HealthIndicator> 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<String, HealthIndicator> 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"))));