diff --git a/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.java b/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.java index e9b372242781..edbc2f35ae43 100644 --- a/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.java +++ b/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.java @@ -28,6 +28,9 @@ * {@link SpringBootCondition} that applies when running in a non-reactive web application * or virtual threads are enabled. * + * Should be kept in sync with + * {@code org.springframework.boot.restclient.autoconfigure.service.NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition}. + * * @author Dmitry Sulman */ class NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition extends AnyNestedCondition { diff --git a/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfiguration.java b/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfiguration.java index 60926d6fb210..0605da85fb31 100644 --- a/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfiguration.java +++ b/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfiguration.java @@ -46,7 +46,7 @@ @AutoConfiguration(after = { ImperativeHttpClientAutoConfiguration.class, RestClientAutoConfiguration.class }) @ConditionalOnClass(RestClientAdapter.class) @ConditionalOnBean(HttpServiceProxyRegistry.class) -@Conditional(NotReactiveWebApplicationCondition.class) +@Conditional(NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.class) @EnableConfigurationProperties(HttpServiceClientProperties.class) public final class HttpServiceClientAutoConfiguration { diff --git a/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.java b/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.java new file mode 100644 index 000000000000..0c0347d036bb --- /dev/null +++ b/module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.restclient.autoconfigure.service; + +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; +import org.springframework.boot.thread.Threading; +import org.springframework.context.annotation.Conditional; + +/** + * {@link SpringBootCondition} that applies when running in a non-reactive web application + * or virtual threads are enabled. + * + * Package-private by design to avoid exposing conditions as public API. Should be kept in + * sync with + * {@code org.springframework.boot.restclient.autoconfigure.NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition}. + * + * @author Dmitry Sulman + */ +class NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition extends AnyNestedCondition { + + NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @Conditional(NotReactiveWebApplicationCondition.class) + private static final class NotReactiveWebApplication { + + } + + @ConditionalOnThreading(Threading.VIRTUAL) + @ConditionalOnBean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) + private static final class VirtualThreadsExecutorEnabled { + + } + +} diff --git a/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfigurationTests.java b/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfigurationTests.java index 11d0ca19baef..4b1f86beba0c 100644 --- a/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfigurationTests.java +++ b/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfigurationTests.java @@ -35,6 +35,7 @@ import org.springframework.aop.Advisor; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; import org.springframework.boot.http.client.HttpClientSettings; import org.springframework.boot.http.client.HttpRedirects; @@ -43,6 +44,7 @@ import org.springframework.boot.restclient.RestClientCustomizer; import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; @@ -54,10 +56,12 @@ import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer; import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.registry.HttpServiceGroup; +import org.springframework.web.service.registry.HttpServiceGroup.ClientType; import org.springframework.web.service.registry.HttpServiceGroupConfigurer.ClientCallback; import org.springframework.web.service.registry.HttpServiceGroupConfigurer.Groups; import org.springframework.web.service.registry.HttpServiceProxyRegistry; import org.springframework.web.service.registry.ImportHttpServices; +import org.springframework.web.util.UriComponentsBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -217,6 +221,23 @@ void whenHasNoHttpServiceProxyRegistryBean() { .run((context) -> assertThat(context).doesNotHaveBean(HttpServiceProxyRegistry.class)); } + @Test + void restClientServiceClientsApplyPropertiesWhenReactiveWithVirtualThreads() { + new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(HttpServiceClientAutoConfiguration.class, + ImperativeHttpClientAutoConfiguration.class, RestClientAutoConfiguration.class, + TaskExecutionAutoConfiguration.class)) + .withPropertyValues("spring.threads.virtual.enabled=true", + "spring.http.serviceclient.echo.base-url=https://example.com") + .withUserConfiguration(ReactiveHttpClientConfiguration.class) + .run((context) -> { + RestClient restClient = getRestClient(context.getBean(ReactiveTestClient.class)); + UriComponentsBuilder baseUri = (UriComponentsBuilder) Extractors.byName("uriBuilderFactory.baseUri") + .apply(restClient); + assertThat(baseUri.build().toUriString()).isEqualTo("https://example.com"); + }); + } + private HttpClient getJdkHttpClient(Object proxy) { return (HttpClient) Extractors.byName("clientRequestFactory.httpClient").apply(getRestClient(proxy)); } @@ -315,4 +336,17 @@ interface TestClientTwo { } + @Configuration(proxyBeanMethods = false) + @ImportHttpServices(types = ReactiveTestClient.class, clientType = ClientType.REST_CLIENT, group = "echo") + static class ReactiveHttpClientConfiguration { + + } + + interface ReactiveTestClient { + + @GetExchange("/echo") + String echo(); + + } + }