Commit ae5ae728 authored by Phillip Webb's avatar Phillip Webb

Add and adapt reactive health contributors

Update `HealthEndpointConfiguration` to also include adapted reactive
health contributors when project reactor is on the classpath. Prior to
this commit, reactive contributors were only exposed in WebFlux
applications. This was a regression from Spring Boot 2.1 that we didn't
catch because all our own reactive contributors all have non-reactive
equivalents.

Closes gh-18805
parent b3e9a064
......@@ -16,13 +16,23 @@
package org.springframework.boot.actuate.autoconfigure.health;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.NamedContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper;
import org.springframework.boot.actuate.health.SimpleStatusAggregator;
import org.springframework.boot.actuate.health.StatusAggregator;
......@@ -30,6 +40,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
/**
* Configuration for {@link HealthEndpoint} infrastructure beans.
......@@ -61,8 +72,13 @@ class HealthEndpointConfiguration {
@Bean
@ConditionalOnMissingBean
HealthContributorRegistry healthContributorRegistry(Map<String, HealthContributor> healthContributors,
HealthContributorRegistry healthContributorRegistry(ApplicationContext applicationContext,
HealthEndpointGroups groups) {
Map<String, HealthContributor> healthContributors = new LinkedHashMap<>(
applicationContext.getBeansOfType(HealthContributor.class));
if (ClassUtils.isPresent("reactor.core.publisher.Flux", applicationContext.getClassLoader())) {
healthContributors.putAll(new AdaptedReactiveHealthContributors(applicationContext).get());
}
return new AutoConfiguredHealthContributorRegistry(healthContributors, groups.getNames());
}
......@@ -72,4 +88,81 @@ class HealthEndpointConfiguration {
return new HealthEndpoint(registry, groups);
}
/**
* Adapter to expose {@link ReactiveHealthContributor} beans as
* {@link HealthContributor} instances.
*/
private static class AdaptedReactiveHealthContributors {
private final Map<String, HealthContributor> adapted;
AdaptedReactiveHealthContributors(ApplicationContext applicationContext) {
Map<String, HealthContributor> adapted = new LinkedHashMap<>();
applicationContext.getBeansOfType(ReactiveHealthContributor.class)
.forEach((name, contributor) -> adapted.put(name, adapt(contributor)));
this.adapted = Collections.unmodifiableMap(adapted);
}
private HealthContributor adapt(ReactiveHealthContributor contributor) {
if (contributor instanceof ReactiveHealthIndicator) {
return adapt((ReactiveHealthIndicator) contributor);
}
if (contributor instanceof CompositeReactiveHealthContributor) {
return adapt((CompositeReactiveHealthContributor) contributor);
}
throw new IllegalStateException("Unsupported ReactiveHealthContributor type " + contributor.getClass());
}
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
return new HealthIndicator() {
@Override
public Health getHealth(boolean includeDetails) {
return indicator.getHealth(includeDetails).block();
}
@Override
public Health health() {
return indicator.health().block();
}
};
}
private CompositeHealthContributor adapt(CompositeReactiveHealthContributor composite) {
return new CompositeHealthContributor() {
@Override
public Iterator<NamedContributor<HealthContributor>> iterator() {
Iterator<NamedContributor<ReactiveHealthContributor>> iterator = composite.iterator();
return new Iterator<NamedContributor<HealthContributor>>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public NamedContributor<HealthContributor> next() {
NamedContributor<ReactiveHealthContributor> next = iterator.next();
return NamedContributor.of(next.getName(), adapt(next.getContributor()));
}
};
}
@Override
public HealthContributor getContributor(String name) {
return adapt(composite.getContributor(name));
}
};
}
Map<String, HealthContributor> get() {
return this.adapted;
}
}
}
......@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.SecurityContext;
......@@ -178,6 +179,16 @@ class HealthEndpointAutoConfigurationTests {
@Test
void runCreatesHealthContributorRegistryContainingHealthBeans() {
this.contextRunner.run((context) -> {
HealthContributorRegistry registry = context.getBean(HealthContributorRegistry.class);
Object[] names = registry.stream().map(NamedContributor::getName).toArray();
assertThat(names).containsExactlyInAnyOrder("simple", "additional", "ping", "reactive");
});
}
@Test
void runWhenNoReactorCreatesHealthContributorRegistryContainingHealthBeans() {
ClassLoader classLoader = new FilteredClassLoader(Mono.class, Flux.class);
this.contextRunner.withClassLoader(classLoader).run((context) -> {
HealthContributorRegistry registry = context.getBean(HealthContributorRegistry.class);
Object[] names = registry.stream().map(NamedContributor::getName).toArray();
assertThat(names).containsExactlyInAnyOrder("simple", "additional", "ping");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment