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
This commit is contained in:
parent
b3e9a06476
commit
ae5ae72889
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Reference in New Issue