Skip to content

Commit 0c552b8

Browse files
committed
Add Support to CompletableFuture as return type in http service method.
// gh-34748 Signed-off-by: Mengqi Xu <[email protected]>
1 parent 12146c4 commit 0c552b8

File tree

2 files changed

+92
-11
lines changed

2 files changed

+92
-11
lines changed

spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.ArrayList;
2323
import java.util.List;
2424
import java.util.Optional;
25+
import java.util.concurrent.CompletableFuture;
2526
import java.util.function.Function;
2627
import java.util.function.Supplier;
2728

@@ -63,6 +64,7 @@
6364
* @author Sebastien Deleuze
6465
* @author Olga Maciaszek-Sharma
6566
* @author Sam Brannen
67+
* @author Mengqi Xu
6668
* @since 6.0
6769
*/
6870
final class HttpServiceMethod {
@@ -412,7 +414,29 @@ public static ResponseFunction create(HttpExchangeAdapter client, Method method)
412414
"Kotlin Coroutines are only supported with reactive implementations");
413415
}
414416

415-
MethodParameter param = new MethodParameter(method, -1).nestedIfOptional();
417+
MethodParameter param = new MethodParameter(method, -1);
418+
Class<?> paramType = param.getNestedParameterType();
419+
420+
Function<HttpRequestValues, @Nullable Object> responseFunction;
421+
if (paramType.equals(CompletableFuture.class)) {
422+
MethodParameter bodyParam = param.nested();
423+
MethodParameter nestedParamIfOptional = bodyParam.getNestedParameterType().equals(Optional.class) ?
424+
bodyParam.nested() : bodyParam;
425+
responseFunction = request ->
426+
CompletableFuture.supplyAsync(() ->
427+
asOptionalIfNecessary(buildResponseFunction(client, nestedParamIfOptional).apply(request),
428+
bodyParam.getNestedParameterType()));
429+
}
430+
else {
431+
responseFunction = request ->
432+
asOptionalIfNecessary(buildResponseFunction(client, param.nestedIfOptional()).apply(request),
433+
param.getParameterType());
434+
}
435+
436+
return new ExchangeResponseFunction(responseFunction);
437+
}
438+
439+
private static Function<HttpRequestValues, @Nullable Object> buildResponseFunction(HttpExchangeAdapter client, MethodParameter param) {
416440
Class<?> paramType = param.getNestedParameterType();
417441

418442
Function<HttpRequestValues, @Nullable Object> responseFunction;
@@ -423,33 +447,30 @@ public static ResponseFunction create(HttpExchangeAdapter client, Method method)
423447
};
424448
}
425449
else if (paramType.equals(HttpHeaders.class)) {
426-
responseFunction = request -> asOptionalIfNecessary(client.exchangeForHeaders(request), param);
450+
responseFunction = client::exchangeForHeaders;
427451
}
428452
else if (paramType.equals(ResponseEntity.class)) {
429453
MethodParameter bodyParam = param.nested();
430454
if (bodyParam.getNestedParameterType().equals(Void.class)) {
431-
responseFunction = request ->
432-
asOptionalIfNecessary(client.exchangeForBodilessEntity(request), param);
455+
responseFunction = client::exchangeForBodilessEntity;
433456
}
434457
else {
435458
ParameterizedTypeReference<?> bodyTypeRef =
436459
ParameterizedTypeReference.forType(bodyParam.getNestedGenericParameterType());
437-
responseFunction = request ->
438-
asOptionalIfNecessary(client.exchangeForEntity(request, bodyTypeRef), param);
460+
responseFunction = request -> client.exchangeForEntity(request, bodyTypeRef);
439461
}
440462
}
441463
else {
442464
ParameterizedTypeReference<?> bodyTypeRef =
443465
ParameterizedTypeReference.forType(param.getNestedGenericParameterType());
444-
responseFunction = request ->
445-
asOptionalIfNecessary(client.exchangeForBody(request, bodyTypeRef), param);
466+
responseFunction = request -> client.exchangeForBody(request, bodyTypeRef);
446467
}
447468

448-
return new ExchangeResponseFunction(responseFunction);
469+
return responseFunction;
449470
}
450471

451-
private static @Nullable Object asOptionalIfNecessary(@Nullable Object response, MethodParameter param) {
452-
return param.getParameterType().equals(Optional.class) ? Optional.ofNullable(response) : response;
472+
private static @Nullable Object asOptionalIfNecessary(@Nullable Object response, Class<?> type) {
473+
return type.equals(Optional.class) ? Optional.ofNullable(response) : response;
453474
}
454475
}
455476

spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.lang.reflect.Method;
2424
import java.util.List;
2525
import java.util.Optional;
26+
import java.util.concurrent.CompletableFuture;
27+
import java.util.concurrent.ExecutionException;
2628

2729
import io.reactivex.rxjava3.core.Completable;
2830
import io.reactivex.rxjava3.core.Flowable;
@@ -61,6 +63,7 @@
6163
* @author Rossen Stoyanchev
6264
* @author Olga Maciaszek-Sharma
6365
* @author Sam Brannen
66+
* @author Mengqi Xu
6467
*/
6568
class HttpServiceMethodTests {
6669

@@ -103,6 +106,34 @@ void service() {
103106
assertThat(list).containsOnly("exchangeForBody");
104107
}
105108

109+
@Test // gh-34748
110+
void completableFutureService() throws ExecutionException, InterruptedException {
111+
CompletableFutureService service = this.proxyFactory.createClient(CompletableFutureService.class);
112+
113+
service.execute();
114+
115+
HttpHeaders headers = service.getHeaders().get();
116+
assertThat(headers).isNotNull();
117+
118+
String body = service.getBody().get();
119+
assertThat(body).isEqualTo(this.client.getInvokedMethodName());
120+
121+
Optional<String> optional = service.getBodyOptional().get();
122+
assertThat(optional.get()).isEqualTo("exchangeForBody");
123+
124+
ResponseEntity<String> entity = service.getEntity().get();
125+
assertThat(entity.getBody()).isEqualTo("exchangeForEntity");
126+
127+
Optional<ResponseEntity<String>> entityOptional = service.getEntityOptional().get();
128+
assertThat(entityOptional.get().getBody()).isEqualTo("exchangeForEntity");
129+
130+
ResponseEntity<Void> voidEntity = service.getVoidEntity().get();
131+
assertThat(voidEntity.getBody()).isNull();
132+
133+
List<String> list = service.getList().get();
134+
assertThat(list).containsOnly("exchangeForBody");
135+
}
136+
106137
@Test
107138
void reactorService() {
108139
ReactorService service = this.reactorProxyFactory.createClient(ReactorService.class);
@@ -294,6 +325,35 @@ private interface Service {
294325

295326
}
296327

328+
@SuppressWarnings("unused")
329+
private interface CompletableFutureService {
330+
331+
@GetExchange
332+
CompletableFuture<Void> execute();
333+
334+
@GetExchange
335+
CompletableFuture<HttpHeaders> getHeaders();
336+
337+
@GetExchange
338+
CompletableFuture<String> getBody();
339+
340+
@GetExchange
341+
CompletableFuture<Optional<String>> getBodyOptional();
342+
343+
@GetExchange
344+
CompletableFuture<ResponseEntity<Void>> getVoidEntity();
345+
346+
@GetExchange
347+
CompletableFuture<ResponseEntity<String>> getEntity();
348+
349+
@GetExchange
350+
CompletableFuture<Optional<ResponseEntity<String>>> getEntityOptional();
351+
352+
@GetExchange
353+
CompletableFuture<List<String>> getList();
354+
355+
}
356+
297357

298358
private interface ReactorService {
299359

0 commit comments

Comments
 (0)