Add Support to CompletableFuture as return type in http service method.

// gh-34748

Signed-off-by: Mengqi Xu <2663479778@qq.com>
This commit is contained in:
Mengqi Xu 2025-06-22 01:43:50 +08:00
parent 12146c4a1b
commit 0c552b8652
2 changed files with 92 additions and 11 deletions

View File

@ -22,6 +22,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Supplier;
@ -63,6 +64,7 @@ import org.springframework.web.service.annotation.HttpExchange;
* @author Sebastien Deleuze
* @author Olga Maciaszek-Sharma
* @author Sam Brannen
* @author Mengqi Xu
* @since 6.0
*/
final class HttpServiceMethod {
@ -412,7 +414,29 @@ final class HttpServiceMethod {
"Kotlin Coroutines are only supported with reactive implementations");
}
MethodParameter param = new MethodParameter(method, -1).nestedIfOptional();
MethodParameter param = new MethodParameter(method, -1);
Class<?> paramType = param.getNestedParameterType();
Function<HttpRequestValues, @Nullable Object> responseFunction;
if (paramType.equals(CompletableFuture.class)) {
MethodParameter bodyParam = param.nested();
MethodParameter nestedParamIfOptional = bodyParam.getNestedParameterType().equals(Optional.class) ?
bodyParam.nested() : bodyParam;
responseFunction = request ->
CompletableFuture.supplyAsync(() ->
asOptionalIfNecessary(buildResponseFunction(client, nestedParamIfOptional).apply(request),
bodyParam.getNestedParameterType()));
}
else {
responseFunction = request ->
asOptionalIfNecessary(buildResponseFunction(client, param.nestedIfOptional()).apply(request),
param.getParameterType());
}
return new ExchangeResponseFunction(responseFunction);
}
private static Function<HttpRequestValues, @Nullable Object> buildResponseFunction(HttpExchangeAdapter client, MethodParameter param) {
Class<?> paramType = param.getNestedParameterType();
Function<HttpRequestValues, @Nullable Object> responseFunction;
@ -423,33 +447,30 @@ final class HttpServiceMethod {
};
}
else if (paramType.equals(HttpHeaders.class)) {
responseFunction = request -> asOptionalIfNecessary(client.exchangeForHeaders(request), param);
responseFunction = client::exchangeForHeaders;
}
else if (paramType.equals(ResponseEntity.class)) {
MethodParameter bodyParam = param.nested();
if (bodyParam.getNestedParameterType().equals(Void.class)) {
responseFunction = request ->
asOptionalIfNecessary(client.exchangeForBodilessEntity(request), param);
responseFunction = client::exchangeForBodilessEntity;
}
else {
ParameterizedTypeReference<?> bodyTypeRef =
ParameterizedTypeReference.forType(bodyParam.getNestedGenericParameterType());
responseFunction = request ->
asOptionalIfNecessary(client.exchangeForEntity(request, bodyTypeRef), param);
responseFunction = request -> client.exchangeForEntity(request, bodyTypeRef);
}
}
else {
ParameterizedTypeReference<?> bodyTypeRef =
ParameterizedTypeReference.forType(param.getNestedGenericParameterType());
responseFunction = request ->
asOptionalIfNecessary(client.exchangeForBody(request, bodyTypeRef), param);
responseFunction = request -> client.exchangeForBody(request, bodyTypeRef);
}
return new ExchangeResponseFunction(responseFunction);
return responseFunction;
}
private static @Nullable Object asOptionalIfNecessary(@Nullable Object response, MethodParameter param) {
return param.getParameterType().equals(Optional.class) ? Optional.ofNullable(response) : response;
private static @Nullable Object asOptionalIfNecessary(@Nullable Object response, Class<?> type) {
return type.equals(Optional.class) ? Optional.ofNullable(response) : response;
}
}

View File

@ -23,6 +23,8 @@ import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
@ -61,6 +63,7 @@ import static org.springframework.http.MediaType.APPLICATION_NDJSON_VALUE;
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
* @author Sam Brannen
* @author Mengqi Xu
*/
class HttpServiceMethodTests {
@ -103,6 +106,34 @@ class HttpServiceMethodTests {
assertThat(list).containsOnly("exchangeForBody");
}
@Test // gh-34748
void completableFutureService() throws ExecutionException, InterruptedException {
CompletableFutureService service = this.proxyFactory.createClient(CompletableFutureService.class);
service.execute();
HttpHeaders headers = service.getHeaders().get();
assertThat(headers).isNotNull();
String body = service.getBody().get();
assertThat(body).isEqualTo(this.client.getInvokedMethodName());
Optional<String> optional = service.getBodyOptional().get();
assertThat(optional.get()).isEqualTo("exchangeForBody");
ResponseEntity<String> entity = service.getEntity().get();
assertThat(entity.getBody()).isEqualTo("exchangeForEntity");
Optional<ResponseEntity<String>> entityOptional = service.getEntityOptional().get();
assertThat(entityOptional.get().getBody()).isEqualTo("exchangeForEntity");
ResponseEntity<Void> voidEntity = service.getVoidEntity().get();
assertThat(voidEntity.getBody()).isNull();
List<String> list = service.getList().get();
assertThat(list).containsOnly("exchangeForBody");
}
@Test
void reactorService() {
ReactorService service = this.reactorProxyFactory.createClient(ReactorService.class);
@ -294,6 +325,35 @@ class HttpServiceMethodTests {
}
@SuppressWarnings("unused")
private interface CompletableFutureService {
@GetExchange
CompletableFuture<Void> execute();
@GetExchange
CompletableFuture<HttpHeaders> getHeaders();
@GetExchange
CompletableFuture<String> getBody();
@GetExchange
CompletableFuture<Optional<String>> getBodyOptional();
@GetExchange
CompletableFuture<ResponseEntity<Void>> getVoidEntity();
@GetExchange
CompletableFuture<ResponseEntity<String>> getEntity();
@GetExchange
CompletableFuture<Optional<ResponseEntity<String>>> getEntityOptional();
@GetExchange
CompletableFuture<List<String>> getList();
}
private interface ReactorService {