From bda889997dfb5a91c76d97d7e2758566bea36b4b Mon Sep 17 00:00:00 2001 From: Lee JiWon Date: Sat, 4 Jul 2026 17:48:46 +0900 Subject: [PATCH 1/2] 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 7a472afcf967..c6c7ffcabe6a 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 @@ private CorsConfiguration getCorsConfiguration() { 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 67925383d253..fe1883d7a4b9 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.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 @@ void close() { 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 From a0c8fa445bc4b179b9bd8b3168c986ea42aa692e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 14 Jul 2026 11:09:17 +0200 Subject: [PATCH 2/2] Polish "Guard reactive Cloud Foundry health extension" See gh-50916 --- ...eactiveActuatorAutoConfigurationTests.java | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) 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 fe1883d7a4b9..d1dddecce07c 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 @@ -97,27 +97,18 @@ class CloudFoundryReactiveActuatorAutoConfigurationTests { private static final String V3_JSON = ApiVersion.V3.getProducedMimeType().toString(); - private final ReactiveWebApplicationContextRunner contextRunner = 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, HealthContributorAutoConfiguration.class, - HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class, - InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, - ProjectInfoAutoConfiguration.class, CloudFoundryReactiveActuatorAutoConfiguration.class)) - .withUserConfiguration(UserDetailsServiceConfiguration.class); + private static final AutoConfigurations MANDATORY_AUTO_CONFIGURATIONS = 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); - 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)) + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(MANDATORY_AUTO_CONFIGURATIONS) + .withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class, + HealthContributorRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class)) .withUserConfiguration(UserDetailsServiceConfiguration.class); private static final String BASE_PATH = "/cloudfoundryapplication"; @@ -130,7 +121,8 @@ void close() { @Test @ClassPathExclusions(packages = "org.springframework.boot.health.actuate.endpoint") void refreshSucceedsWithoutHealth() { - this.withoutHealthContextRunner + new ReactiveWebApplicationContextRunner().withConfiguration(MANDATORY_AUTO_CONFIGURATIONS) + .withUserConfiguration(UserDetailsServiceConfiguration.class) .withPropertyValues("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", "vcap.application.cf_api:https://my-cloud-controller.com") .run((context) -> assertThat(context).hasNotFailed());