diff --git a/module/spring-boot-health/src/main/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointConfiguration.java b/module/spring-boot-health/src/main/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointConfiguration.java index 9fcf2e59470..43f74c19a8f 100644 --- a/module/spring-boot-health/src/main/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointConfiguration.java +++ b/module/spring-boot-health/src/main/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.health.autoconfigure.actuate.endpoint; import java.util.Set; +import java.util.function.BiFunction; import org.jspecify.annotations.Nullable; @@ -34,6 +35,7 @@ import org.springframework.boot.health.actuate.endpoint.SimpleHttpCodeStatusMapp import org.springframework.boot.health.actuate.endpoint.SimpleStatusAggregator; import org.springframework.boot.health.actuate.endpoint.StatusAggregator; import org.springframework.boot.health.contributor.HealthContributors; +import org.springframework.boot.health.contributor.ReactiveHealthContributors; import org.springframework.boot.health.registry.HealthContributorRegistry; import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry; import org.springframework.context.ApplicationContext; @@ -78,8 +80,10 @@ class HealthEndpointConfiguration { @Bean @ConditionalOnBooleanProperty(name = "management.endpoint.health.validate-group-membership", matchIfMissing = true) HealthEndpointGroupMembershipValidator healthEndpointGroupMembershipValidator(HealthEndpointProperties properties, - HealthContributorRegistry healthContributorRegistry) { - return new HealthEndpointGroupMembershipValidator(properties, healthContributorRegistry); + HealthContributorRegistry healthContributorRegistry, + ObjectProvider reactiveHealthContributorRegistry) { + return new HealthEndpointGroupMembershipValidator(properties, healthContributorRegistry, + reactiveHealthContributorRegistry.getIfAvailable()); } @Bean @@ -138,10 +142,13 @@ class HealthEndpointConfiguration { private final HealthContributorRegistry registry; - HealthEndpointGroupMembershipValidator(HealthEndpointProperties properties, - HealthContributorRegistry registry) { + @Nullable ReactiveHealthContributorRegistry fallbackRegistry; + + HealthEndpointGroupMembershipValidator(HealthEndpointProperties properties, HealthContributorRegistry registry, + @Nullable ReactiveHealthContributorRegistry fallbackRegistry) { this.properties = properties; this.registry = registry; + this.fallbackRegistry = fallbackRegistry; } @Override @@ -172,13 +179,28 @@ class HealthEndpointConfiguration { } private boolean contributorExists(String[] path) { + return contributorExistsInMainRegistry(path) || contributorExistsInFallbackRegistry(path); + } + + private boolean contributorExistsInMainRegistry(String[] path) { + return contributorExists(path, this.registry, HealthContributors.class, HealthContributors::getContributor); + } + + private boolean contributorExistsInFallbackRegistry(String[] path) { + return contributorExists(path, this.fallbackRegistry, ReactiveHealthContributors.class, + ReactiveHealthContributors::getContributor); + } + + @SuppressWarnings("unchecked") + private boolean contributorExists(String[] path, @Nullable Object registry, Class collectionType, + BiFunction getFromCollection) { int pathOffset = 0; - Object contributor = this.registry; + Object contributor = registry; while (pathOffset < path.length) { - if (!(contributor instanceof HealthContributors)) { + if (contributor == null || !collectionType.isInstance(contributor)) { return false; } - contributor = ((HealthContributors) contributor).getContributor(path[pathOffset]); + contributor = getFromCollection.apply((C) contributor, path[pathOffset]); pathOffset++; } return (contributor != null); diff --git a/module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointAutoConfigurationTests.java b/module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointAutoConfigurationTests.java index 3d0268c1854..960723cbe38 100644 --- a/module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointAutoConfigurationTests.java +++ b/module/spring-boot-health/src/test/java/org/springframework/boot/health/autoconfigure/actuate/endpoint/HealthEndpointAutoConfigurationTests.java @@ -44,6 +44,7 @@ import org.springframework.boot.health.autoconfigure.actuate.endpoint.HealthEndp import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration; import org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration; import org.springframework.boot.health.contributor.CompositeHealthContributor; +import org.springframework.boot.health.contributor.CompositeReactiveHealthContributor; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.HealthContributors; import org.springframework.boot.health.contributor.HealthIndicator; @@ -145,6 +146,20 @@ class HealthEndpointAutoConfigurationTests { }); } + @Test + void runDoesNotFailWhenHealthEndpointGroupIncludesContributorThatExists() { + this.contextRunner.withUserConfiguration(CompositeHealthIndicatorConfiguration.class) + .withPropertyValues("management.endpoint.health.group.ready.include=composite/b/c") + .run((context) -> assertThat(context).hasNotFailed()); + } + + @Test // gh-48387 + void runDoesNotFailWhenHealthEndpointGroupIncludesReactiveContributorThatExists() { + this.contextRunner.withUserConfiguration(CompositeReactiveHealthIndicatorConfiguration.class) + .withPropertyValues("management.endpoint.health.group.ready.include=composite/b/c") + .run((context) -> assertThat(context).hasNotFailed()); + } + @Test void runFailsWhenHealthEndpointGroupIncludesContributorThatDoesNotExist() { this.contextRunner.withUserConfiguration(CompositeHealthIndicatorConfiguration.class) @@ -377,8 +392,27 @@ class HealthEndpointAutoConfigurationTests { @Bean CompositeHealthContributor compositeHealthIndicator() { - return CompositeHealthContributor.fromMap(Map.of("a", (HealthIndicator) () -> Health.up().build(), "b", - CompositeHealthContributor.fromMap(Map.of("c", (HealthIndicator) () -> Health.up().build())))); + return CompositeHealthContributor.fromMap(Map.of("a", createHealthIndicator(), "b", + CompositeHealthContributor.fromMap(Map.of("c", createHealthIndicator())))); + } + + private HealthIndicator createHealthIndicator() { + return () -> Health.up().build(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CompositeReactiveHealthIndicatorConfiguration { + + @Bean + CompositeReactiveHealthContributor compositeHealthIndicator() { + return CompositeReactiveHealthContributor.fromMap(Map.of("a", createHealthIndicator(), "b", + CompositeReactiveHealthContributor.fromMap(Map.of("c", createHealthIndicator())))); + } + + private ReactiveHealthIndicator createHealthIndicator() { + return () -> Mono.just(Health.up().build()); } }