From bda889997dfb5a91c76d97d7e2758566bea36b4b Mon Sep 17 00:00:00 2001 From: Lee JiWon Date: Sat, 4 Jul 2026 17:48:46 +0900 Subject: [PATCH] Guard reactive Cloud Foundry health extension Move the reactive Cloud Foundry health endpoint extension into a health-specific configuration guarded by @ConditionalOnClass. When spring-boot-health is absent, scanning the outer configuration attempts to resolve ReactiveHealthEndpointWebExtension and causes context refresh to fail. See gh-50916 Signed-off-by: Lee JiWon --- ...ndryReactiveActuatorAutoConfiguration.java | 24 ++++++++++++------- ...eactiveActuatorAutoConfigurationTests.java | 21 ++++++++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java index 7a472afcf96..c6c7ffcabe6 100644 --- a/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java +++ b/module/spring-boot-cloudfoundry/src/main/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfiguration.java @@ -83,15 +83,6 @@ public final class CloudFoundryReactiveActuatorAutoConfiguration { private static final String BASE_PATH = "/cloudfoundryapplication"; - @Bean - @ConditionalOnMissingBean - @ConditionalOnAvailableEndpoint - @ConditionalOnBean({ HealthEndpoint.class, ReactiveHealthEndpointWebExtension.class }) - CloudFoundryReactiveHealthEndpointWebExtension cloudFoundryReactiveHealthEndpointWebExtension( - ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension) { - return new CloudFoundryReactiveHealthEndpointWebExtension(reactiveHealthEndpointWebExtension); - } - @Bean @ConditionalOnMissingBean @ConditionalOnAvailableEndpoint @@ -151,6 +142,21 @@ public final class CloudFoundryReactiveActuatorAutoConfiguration { return corsConfiguration; } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(HealthEndpoint.class) + static class HealthConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnAvailableEndpoint + @ConditionalOnBean({ HealthEndpoint.class, ReactiveHealthEndpointWebExtension.class }) + CloudFoundryReactiveHealthEndpointWebExtension cloudFoundryReactiveHealthEndpointWebExtension( + ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension) { + return new CloudFoundryReactiveHealthEndpointWebExtension(reactiveHealthEndpointWebExtension); + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(MatcherSecurityWebFilterChain.class) static class IgnoredPathsSecurityConfiguration { diff --git a/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfigurationTests.java b/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfigurationTests.java index 67925383d25..fe1883d7a4b 100644 --- a/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfigurationTests.java +++ b/module/spring-boot-cloudfoundry/src/test/java/org/springframework/boot/cloudfoundry/autoconfigure/actuate/endpoint/reactive/CloudFoundryReactiveActuatorAutoConfigurationTests.java @@ -59,6 +59,7 @@ import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.jks.JksSslStoreBundle; import org.springframework.boot.ssl.jks.JksSslStoreDetails; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.resources.WithPackageResources; import org.springframework.boot.testsupport.classpath.resources.WithResource; import org.springframework.boot.webclient.WebClientCustomizer; @@ -108,6 +109,17 @@ class CloudFoundryReactiveActuatorAutoConfigurationTests { ProjectInfoAutoConfiguration.class, CloudFoundryReactiveActuatorAutoConfiguration.class)) .withUserConfiguration(UserDetailsServiceConfiguration.class); + private final ReactiveWebApplicationContextRunner withoutHealthContextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(ReactiveWebSecurityAutoConfiguration.class, WebFluxAutoConfiguration.class, + JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class, + WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, + EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, + InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, + ProjectInfoAutoConfiguration.class, CloudFoundryReactiveActuatorAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class); + private static final String BASE_PATH = "/cloudfoundryapplication"; @AfterEach @@ -115,6 +127,15 @@ class CloudFoundryReactiveActuatorAutoConfigurationTests { HttpResources.reset(); } + @Test + @ClassPathExclusions(packages = "org.springframework.boot.health.actuate.endpoint") + void refreshSucceedsWithoutHealth() { + this.withoutHealthContextRunner + .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", + "vcap.application.cf_api:https://my-cloud-controller.com") + .run((context) -> assertThat(context).hasNotFailed()); + } + @Test void cloudFoundryPlatformActive() { this.contextRunner