Skip to content

Commit a599e74

Browse files
committed
Support virtual threads in HTTP Service Client autoconfiguration
Previously, HttpServiceClientAutoConfiguration used NotReactiveWebApplicationCondition, which prevented activation in reactive apps even when virtual threads were enabled. This commit updates the condition to NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition, allowing HTTP Service Clients to work in reactive apps when virtual threads are enabled, matching the behavior of RestClientAutoConfiguration. Closes gh-48273 Signed-off-by: Nhahan <kisy324@naver.com>
1 parent 0a40378 commit a599e74

File tree

3 files changed

+91
-2
lines changed

3 files changed

+91
-2
lines changed

module/spring-boot-restclient/src/main/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
import org.springframework.web.service.registry.HttpServiceProxyRegistry;
3737

3838
/**
39-
* AutoConfiguration for Spring HTTP Service clients backed by {@link RestClient}.
39+
* AutoConfiguration for Spring HTTP Service clients backed by
40+
* {@link RestClient}.
4041
*
4142
* @author Olga Maciaszek-Sharma
4243
* @author Rossen Stoyanchev
@@ -46,7 +47,7 @@
4647
@AutoConfiguration(after = { ImperativeHttpClientAutoConfiguration.class, RestClientAutoConfiguration.class })
4748
@ConditionalOnClass(RestClientAdapter.class)
4849
@ConditionalOnBean(HttpServiceProxyRegistry.class)
49-
@Conditional(NotReactiveWebApplicationCondition.class)
50+
@Conditional(NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition.class)
5051
@EnableConfigurationProperties(HttpServiceClientProperties.class)
5152
public final class HttpServiceClientAutoConfiguration {
5253

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.restclient.autoconfigure.service;
18+
19+
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
22+
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
23+
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
24+
import org.springframework.boot.thread.Threading;
25+
import org.springframework.context.annotation.Conditional;
26+
27+
/**
28+
* {@link SpringBootCondition} that applies when running in a non-reactive web application
29+
* or virtual threads are enabled.
30+
*
31+
* Package-private by design to avoid exposing conditions as public API. Should be kept in
32+
* sync with
33+
* {@code org.springframework.boot.restclient.autoconfigure.NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition}.
34+
*
35+
* @author Dmitry Sulman
36+
*/
37+
class NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition extends AnyNestedCondition {
38+
39+
NotReactiveWebApplicationOrVirtualThreadsExecutorEnabledCondition() {
40+
super(ConfigurationPhase.REGISTER_BEAN);
41+
}
42+
43+
@Conditional(NotReactiveWebApplicationCondition.class)
44+
private static final class NotReactiveWebApplication {
45+
46+
}
47+
48+
@ConditionalOnThreading(Threading.VIRTUAL)
49+
@ConditionalOnBean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
50+
private static final class VirtualThreadsExecutorEnabled {
51+
52+
}
53+
54+
}

module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/service/HttpServiceClientAutoConfigurationTests.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
import org.springframework.aop.Advisor;
3737
import org.springframework.boot.autoconfigure.AutoConfigurations;
38+
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
3839
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
3940
import org.springframework.boot.http.client.HttpClientSettings;
4041
import org.springframework.boot.http.client.HttpRedirects;
@@ -43,6 +44,7 @@
4344
import org.springframework.boot.restclient.RestClientCustomizer;
4445
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
4546
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
47+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
4648
import org.springframework.context.annotation.Bean;
4749
import org.springframework.context.annotation.Configuration;
4850
import org.springframework.http.client.ClientHttpRequestFactory;
@@ -54,10 +56,12 @@
5456
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
5557
import org.springframework.web.service.annotation.GetExchange;
5658
import org.springframework.web.service.registry.HttpServiceGroup;
59+
import org.springframework.web.service.registry.HttpServiceGroup.ClientType;
5760
import org.springframework.web.service.registry.HttpServiceGroupConfigurer.ClientCallback;
5861
import org.springframework.web.service.registry.HttpServiceGroupConfigurer.Groups;
5962
import org.springframework.web.service.registry.HttpServiceProxyRegistry;
6063
import org.springframework.web.service.registry.ImportHttpServices;
64+
import org.springframework.web.util.UriComponentsBuilder;
6165

6266
import static org.assertj.core.api.Assertions.assertThat;
6367
import static org.mockito.BDDMockito.given;
@@ -217,6 +221,23 @@ void whenHasNoHttpServiceProxyRegistryBean() {
217221
.run((context) -> assertThat(context).doesNotHaveBean(HttpServiceProxyRegistry.class));
218222
}
219223

224+
@Test
225+
void restClientServiceClientsApplyPropertiesWhenReactiveWithVirtualThreads() {
226+
new ReactiveWebApplicationContextRunner()
227+
.withConfiguration(AutoConfigurations.of(HttpServiceClientAutoConfiguration.class,
228+
ImperativeHttpClientAutoConfiguration.class, RestClientAutoConfiguration.class,
229+
TaskExecutionAutoConfiguration.class))
230+
.withPropertyValues("spring.threads.virtual.enabled=true",
231+
"spring.http.serviceclient.echo.base-url=https://example.com")
232+
.withUserConfiguration(ReactiveHttpClientConfiguration.class)
233+
.run((context) -> {
234+
RestClient restClient = getRestClient(context.getBean(ReactiveTestClient.class));
235+
UriComponentsBuilder baseUri = (UriComponentsBuilder) Extractors.byName("uriBuilderFactory.baseUri")
236+
.apply(restClient);
237+
assertThat(baseUri.build().toUriString()).isEqualTo("https://example.com");
238+
});
239+
}
240+
220241
private HttpClient getJdkHttpClient(Object proxy) {
221242
return (HttpClient) Extractors.byName("clientRequestFactory.httpClient").apply(getRestClient(proxy));
222243
}
@@ -315,4 +336,17 @@ interface TestClientTwo {
315336

316337
}
317338

339+
@Configuration(proxyBeanMethods = false)
340+
@ImportHttpServices(types = ReactiveTestClient.class, clientType = ClientType.REST_CLIENT, group = "echo")
341+
static class ReactiveHttpClientConfiguration {
342+
343+
}
344+
345+
interface ReactiveTestClient {
346+
347+
@GetExchange("/echo")
348+
String echo();
349+
350+
}
351+
318352
}

0 commit comments

Comments
 (0)