Make additional health paths back off without health module

Fixes gh-49196
This commit is contained in:
Andy Wilkinson
2026-02-12 10:31:00 +00:00
parent 278de05619
commit 9c6d1d2944
5 changed files with 70 additions and 34 deletions
+5
View File
@@ -63,3 +63,8 @@ dependencies {
tasks.named("compileTestJava") {
options.nullability.checking = "tests"
}
tasks.named("test") {
jvmArgs += "--add-opens=java.base/java.net=ALL-UNNAMED"
}
@@ -58,6 +58,7 @@ import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroups;
import org.springframework.boot.webflux.actuate.endpoint.web.AdditionalHealthEndpointPathsWebFluxHandlerMapping;
import org.springframework.boot.webflux.actuate.endpoint.web.WebFluxEndpointHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.codec.Encoder;
import org.springframework.core.env.Environment;
@@ -113,21 +114,6 @@ public class WebFluxEndpointManagementContextConfiguration {
|| ManagementPortType.get(environment) == ManagementPortType.DIFFERENT);
}
@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
@ConditionalOnBean(HealthEndpoint.class)
public AdditionalHealthEndpointPathsWebFluxHandlerMapping managementHealthEndpointWebFluxHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) {
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
ExposableWebEndpoint healthEndpoint = webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID))
.findFirst()
.orElse(null);
return new AdditionalHealthEndpointPathsWebFluxHandlerMapping(new EndpointMapping(""), healthEndpoint,
groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT));
}
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("removal")
@@ -161,6 +147,27 @@ public class WebFluxEndpointManagementContextConfiguration {
SingletonSupplier.of(endpointJsonMapper::getObject));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HealthEndpoint.class)
static class HealthConfiguration {
@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
@ConditionalOnBean(HealthEndpoint.class)
AdditionalHealthEndpointPathsWebFluxHandlerMapping managementHealthEndpointWebFluxHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) {
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
ExposableWebEndpoint healthEndpoint = webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID))
.findFirst()
.orElse(null);
return new AdditionalHealthEndpointPathsWebFluxHandlerMapping(new EndpointMapping(""), healthEndpoint,
groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT));
}
}
/**
* {@link BeanPostProcessor} to apply {@link EndpointJsonMapper} for
* {@link OperationResponseBody} to {@link JacksonJsonEncoder} instances.
@@ -41,6 +41,8 @@ import org.springframework.boot.env.ConfigTreePropertySource;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
import org.springframework.boot.tomcat.TomcatWebServer;
import org.springframework.boot.tomcat.autoconfigure.actuate.web.server.TomcatReactiveManagementContextAutoConfiguration;
import org.springframework.boot.tomcat.autoconfigure.reactive.TomcatReactiveWebServerAutoConfiguration;
@@ -64,6 +66,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
@DirtiesUrlFactories
class WebFluxManagementChildContextConfigurationIntegrationTests {
private final List<WebServer> webServers = new ArrayList<>();
@@ -124,6 +127,12 @@ class WebFluxManagementChildContextConfigurationIntegrationTests {
});
}
@Test
@ClassPathExclusions(packages = "org.springframework.boot.health.actuate.endpoint")
void refreshSucceedsWithoutHealth() {
this.runner.run((context) -> assertThat(context).hasNotFailed());
}
private @Nullable AccessLogValve findAccessLogValve() {
assertThat(this.webServers).hasSize(2);
Tomcat tomcat = ((TomcatWebServer) this.webServers.get(1)).getTomcat();
@@ -44,6 +44,7 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
@@ -54,6 +55,7 @@ import org.springframework.boot.webmvc.actuate.endpoint.web.AdditionalHealthEndp
import org.springframework.boot.webmvc.actuate.endpoint.web.WebMvcEndpointHandlerMapping;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
@@ -108,25 +110,6 @@ public class WebMvcEndpointManagementContextConfiguration {
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
AdditionalHealthEndpointPathsWebMvcHandlerMapping managementHealthEndpointWebMvcHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) {
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
ExposableWebEndpoint healthEndpoint = webEndpoints.stream()
.filter(this::isHealthEndpoint)
.findFirst()
.orElse(null);
return new AdditionalHealthEndpointPathsWebMvcHandlerMapping(healthEndpoint,
groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT));
}
private boolean isHealthEndpoint(ExposableWebEndpoint endpoint) {
return endpoint.getEndpointId().equals(HealthEndpoint.ID);
}
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("removal")
@@ -169,6 +152,31 @@ public class WebMvcEndpointManagementContextConfiguration {
return new EndpointJackson2ObjectMapperWebMvcConfigurer(endpointJsonMapper);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HealthEndpoint.class)
static class HealthConfiguration {
@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class, exposure = EndpointExposure.WEB)
AdditionalHealthEndpointPathsWebMvcHandlerMapping managementHealthEndpointWebMvcHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) {
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
ExposableWebEndpoint healthEndpoint = webEndpoints.stream()
.filter(this::isHealthEndpoint)
.findFirst()
.orElse(null);
return new AdditionalHealthEndpointPathsWebMvcHandlerMapping(healthEndpoint,
groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT));
}
private boolean isHealthEndpoint(ExposableWebEndpoint endpoint) {
return endpoint.getEndpointId().equals(HealthEndpoint.ID);
}
}
/**
* {@link WebMvcConfigurer} to apply {@link EndpointJsonMapper} for
* {@link OperationResponseBody} to {@link JacksonJsonHttpMessageConverter} instances.
@@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath;
import org.springframework.context.annotation.Bean;
@@ -66,6 +67,12 @@ class WebMvcEndpointManagementContextConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(ServletEndpointRegistrar.class));
}
@Test
@ClassPathExclusions(packages = "org.springframework.boot.health.actuate.endpoint")
void refreshSucceedsWithoutHealth() {
this.contextRunner.run((context) -> assertThat(context).hasNotFailed());
}
@Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration(WebMvcEndpointManagementContextConfiguration.class)
static class TestConfig {