CompletableFuture:如何将两个异步请求合并为一个响应
CompletableFuture: how to combine two asynchronous requests into one response
第一个请求是异步发送的,然后从对第一个请求的响应中取出id值,用在第二个异步请求中。然后合并对请求 1 和请求 2 的响应。下面的示例有效,但不是异步的,因为使用了 .get() 。有没有办法异步执行此操作?
流程简而言之 - 一切都应该异步发生:
- 发送POST请求1
- 将响应 1 中的 ID 值用于请求 2
- 发送POST请求2
- 将响应1和响应2合并为REST控制器的最终响应
此主体通过 POST 方法发送到 REST 控制器端点“/combine”:
{
"userid": 1,
"id": 2,
"title": "3",
"body": "4" }
@RestController
public class CombinationController {
@Autowired
CombinationService combinationService;
@PostMapping("/combine")
public CompletableFuture<CombinationBothResponses> combine(@RequestBody RequestBodyOne requestBodyOne) {
return combinationService.combine(requestBodyOne);
}
}
@Service
public class CombinationService {
private final Jsonb jsonb = JsonbBuilder.create();
private final HttpClient httpClient = HttpClient.newBuilder().build();
public CompletableFuture<CombinationBothResponses> combine(RequestBodyOne requestBodyOne) {
// 1. Send POST request 1
HttpRequest httpRequestOne =
HttpRequest.newBuilder(URI.create("https://jsonplaceholder.typicode.com/posts"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.POST(HttpRequest.BodyPublishers.ofString(jsonb.toJson(requestBodyOne)))
.build();
return httpClient
.sendAsync(httpRequestOne, HttpResponse.BodyHandlers.ofString())
.thenApply(
httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
CombinationBothResponses combinationBothResponses =
jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
// 2. Use one value from response 1 for request 2
int valueToBeUsedInRequestBody2 = combinationBothResponses.getId();
// 3. Send POST request 2
CompletableFuture<CombinationBothResponses> completableFuture2 =
sendSecondPostRequest(valueToBeUsedInRequestBody2);
// 4. Combine response 1 and response 2 to the final response of REST controller
try {
CombinationBothResponses responseBodyRequestTwo =
completableFuture2.get(); // Not asynchronous
combinationBothResponses.setSuccess(responseBodyRequestTwo.getSuccess());
return combinationBothResponses;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
throw new RuntimeException();
});
}
private CompletableFuture<CombinationBothResponses> sendSecondPostRequest(
int valueToBeUsedInRequestBody2) {
RequestBodyTwo requestBodyTwo = new RequestBodyTwo(valueToBeUsedInRequestBody2, "request 2");
HttpRequest httpRequest =
HttpRequest.newBuilder(URI.create("https://reqbin.com/echo/post/json"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.POST(HttpRequest.BodyPublishers.ofString(jsonb.toJson(requestBodyTwo)))
.build();
return httpClient
.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(
httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
CombinationBothResponses responseBodyRequestTwo =
jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
return responseBodyRequestTwo;
}
throw new RuntimeException();
});
}
}
@Data
@NoArgsConstructor
public class RequestBodyOne {
private int userId;
private int id;
private String title;
private String body;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestBodyTwo {
private int id;
private String key;
}
@Data
@NoArgsConstructor
public class CombinationBothResponses {
private int userId;
private int id;
private String title;
private String body;
private String success;
}
对请求 1 的响应:
{ "userId": 0, "id": 101, "title": "3", "body": "4" }
对请求 2 的响应:
{"success":"true"}
综合回复; REST 控制器的响应:
{
"userId": 0,
"id": 101,
"title": "3",
"body": "4",
"success": "true" }
return httpClient
.sendAsync(httpRequestOne, HttpResponse.BodyHandlers.ofString())
.thenCompose(httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
final CombinationBothResponses combinationBothResponses = jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
// 2. Use one value from response 1 for request 2
int valueToBeUsedInRequestBody2 = combinationBothResponses.getId();
// 3. Send POST request 2
return sendSecondPostRequest(valueToBeUsedInRequestBody2)
.thenApply(responseBodyRequestTwo -> {
// 4. Combine response 1 and response 2 to the final response of REST controller
combinationBothResponses.setSuccess(responseBodyRequestTwo.getSuccess());
return combinationBothResponses;
});
}
return CompletableFuture.failedFuture(new RuntimeException());
});
第一个请求是异步发送的,然后从对第一个请求的响应中取出id值,用在第二个异步请求中。然后合并对请求 1 和请求 2 的响应。下面的示例有效,但不是异步的,因为使用了 .get() 。有没有办法异步执行此操作? 流程简而言之 - 一切都应该异步发生:
- 发送POST请求1
- 将响应 1 中的 ID 值用于请求 2
- 发送POST请求2
- 将响应1和响应2合并为REST控制器的最终响应
此主体通过 POST 方法发送到 REST 控制器端点“/combine”:
{ "userid": 1, "id": 2, "title": "3", "body": "4" }
@RestController
public class CombinationController {
@Autowired
CombinationService combinationService;
@PostMapping("/combine")
public CompletableFuture<CombinationBothResponses> combine(@RequestBody RequestBodyOne requestBodyOne) {
return combinationService.combine(requestBodyOne);
}
}
@Service
public class CombinationService {
private final Jsonb jsonb = JsonbBuilder.create();
private final HttpClient httpClient = HttpClient.newBuilder().build();
public CompletableFuture<CombinationBothResponses> combine(RequestBodyOne requestBodyOne) {
// 1. Send POST request 1
HttpRequest httpRequestOne =
HttpRequest.newBuilder(URI.create("https://jsonplaceholder.typicode.com/posts"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.POST(HttpRequest.BodyPublishers.ofString(jsonb.toJson(requestBodyOne)))
.build();
return httpClient
.sendAsync(httpRequestOne, HttpResponse.BodyHandlers.ofString())
.thenApply(
httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
CombinationBothResponses combinationBothResponses =
jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
// 2. Use one value from response 1 for request 2
int valueToBeUsedInRequestBody2 = combinationBothResponses.getId();
// 3. Send POST request 2
CompletableFuture<CombinationBothResponses> completableFuture2 =
sendSecondPostRequest(valueToBeUsedInRequestBody2);
// 4. Combine response 1 and response 2 to the final response of REST controller
try {
CombinationBothResponses responseBodyRequestTwo =
completableFuture2.get(); // Not asynchronous
combinationBothResponses.setSuccess(responseBodyRequestTwo.getSuccess());
return combinationBothResponses;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
throw new RuntimeException();
});
}
private CompletableFuture<CombinationBothResponses> sendSecondPostRequest(
int valueToBeUsedInRequestBody2) {
RequestBodyTwo requestBodyTwo = new RequestBodyTwo(valueToBeUsedInRequestBody2, "request 2");
HttpRequest httpRequest =
HttpRequest.newBuilder(URI.create("https://reqbin.com/echo/post/json"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.POST(HttpRequest.BodyPublishers.ofString(jsonb.toJson(requestBodyTwo)))
.build();
return httpClient
.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(
httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
CombinationBothResponses responseBodyRequestTwo =
jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
return responseBodyRequestTwo;
}
throw new RuntimeException();
});
}
}
@Data
@NoArgsConstructor
public class RequestBodyOne {
private int userId;
private int id;
private String title;
private String body;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestBodyTwo {
private int id;
private String key;
}
@Data
@NoArgsConstructor
public class CombinationBothResponses {
private int userId;
private int id;
private String title;
private String body;
private String success;
}
对请求 1 的响应:
{ "userId": 0, "id": 101, "title": "3", "body": "4" }
对请求 2 的响应:
{"success":"true"}
综合回复; REST 控制器的响应:
{ "userId": 0, "id": 101, "title": "3", "body": "4", "success": "true" }
return httpClient
.sendAsync(httpRequestOne, HttpResponse.BodyHandlers.ofString())
.thenCompose(httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
final CombinationBothResponses combinationBothResponses = jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
// 2. Use one value from response 1 for request 2
int valueToBeUsedInRequestBody2 = combinationBothResponses.getId();
// 3. Send POST request 2
return sendSecondPostRequest(valueToBeUsedInRequestBody2)
.thenApply(responseBodyRequestTwo -> {
// 4. Combine response 1 and response 2 to the final response of REST controller
combinationBothResponses.setSuccess(responseBodyRequestTwo.getSuccess());
return combinationBothResponses;
});
}
return CompletableFuture.failedFuture(new RuntimeException());
});