使用 WebClient/Spring Boot 2 将 REST 响应映射到 Mono<SomeClass> 的正确方法
The correct way to map a REST response to a Mono<SomeClass> using WebClient/ Spring Boot 2
我有一个控制器,它要求服务到达服务端点以获取帐号列表,然后为每个帐号联系另一个服务并检索有关该帐户的其他数据。 'happy path' 本质上是这样的:
控制器
@ApiOperation(value = "getRelatedAccounts")
@GetMapping(value = "/relatedaccounts")
public ResponseEntity<RelatedAccounts> getRelatedAccounts(@RequestParam String accountNumber) {
return new ResponseEntity<>(myService.getRelatedAccounts(accountNumber), HttpStatus.OK);
}
服务
public RelatedAccounts getRelatedAccounts(String accountNumber) {
// blah blah, hit the endpoint and get my response of a list of account numbers in relatedAccountsList
Flux<AccountInformation> accountInfoFlux = Flux.fromIterable(relatedAccountsList)
.parallel()
.runOn(Schedulers.elastic())
.flatMap(this::getAccountInformation)
.ordered(Comparator.comparing(RelatedAccounts::getCorrectOrder)) // blah blah, convert from ParallelFlux to Flux
}
其他服务
public Mono<AccountInformation> getAccountInformation(String accountNumber) {
WebClient webClient = WebClient.builder()
.baseUrl("http://myurl.com")
.build();
return webClient
.get()
.uri(uriBuilder -> uriBuilder
.queryParam("accountNumber", accountNumber)
.build()
).retrieve()
.bodyToMono(AccountInformation.class) // This doesn't work for us, we get a very complex object as a response and need to parse a few fields.
}
我在 Google 上花了一整天,但我没有看到其他人解析他们返回的响应,他们只是神奇地将其直接映射到完美创建的 class 上。我们没有那个选项,我们需要从响应主体中提取 accountName 并将其放入 AccountInformation 对象中。有人知道怎么做吗?
我的建议是继续使用 bodyToMono(AccountInformation.class)
,然后使用 Monos map and zip 映射到您的简单对象。
- 创建一个 class 来表示复杂的 AccountInformation,但只包含您需要的信息(不要包含您不需要的对象字段)。
- 使用您的其他服务(returns Mono)的确切代码
- 您可以使用
.zip
将您的简单对象与 AccountInformation 对象结合起来。
- 因此,您的简单对象只包含复杂对象所需的数据
例如
其他服务保持不变:
public Mono<AccountInformation> getAccountInformation(String accountNumber) {
WebClient webClient = WebClient.builder()
.baseUrl("http://myurl.com")
.build();
return webClient
.get()
.uri(uriBuilder -> uriBuilder
.queryParam("accountNumber", accountNumber)
.build()
).retrieve()
.bodyToMono(AccountInformation.class)
}
创建缺失的class(表示其他服务的结果),跳过所有您不需要的信息字段
import java.util.List;
public class AccountInformation {
private String infoYouWant;
private List<AccountDetails> otherNestedInfoYouWant;
// getters/setters skipped for easier read
}
将您的简单对象与来自其他服务的对象合并:
Mono<AccountInformation> accountInformation = someService.getAccountInformation("fillMeWithAccountNumber");
Mono<SimpleObject> simpleObjectWithAccountInformation = Mono.zip(simpleObject, accountInformation).map(objects -> {
SimpleObject simpleObject = objects.getT1();
simpleObject.setAccountNumber(objects.getT2().getInfoYouWant());
return simpleObject;
});
我们最终采用了一种非常丑陋但可扩展且有效的方法。在我们的其他服务中:
public Mono<AccountInformation> getAccountInformation(String accountNumber) {
WebClient webClient = WebClient.builder()
.baseUrl("http://myurl.com")
.build();
return webClient
.get()
.uri(uriBuilder -> uriBuilder
.queryParam("accountNumber", accountNumber)
.build()
).retrieve()
.bodyToMono(String.class) // just get the response as a JSON string
.map(jsonString -> {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonString);
// parse/ map out all your information here
return myCustomMappedObject;
})
}
我有一个控制器,它要求服务到达服务端点以获取帐号列表,然后为每个帐号联系另一个服务并检索有关该帐户的其他数据。 'happy path' 本质上是这样的:
控制器
@ApiOperation(value = "getRelatedAccounts")
@GetMapping(value = "/relatedaccounts")
public ResponseEntity<RelatedAccounts> getRelatedAccounts(@RequestParam String accountNumber) {
return new ResponseEntity<>(myService.getRelatedAccounts(accountNumber), HttpStatus.OK);
}
服务
public RelatedAccounts getRelatedAccounts(String accountNumber) {
// blah blah, hit the endpoint and get my response of a list of account numbers in relatedAccountsList
Flux<AccountInformation> accountInfoFlux = Flux.fromIterable(relatedAccountsList)
.parallel()
.runOn(Schedulers.elastic())
.flatMap(this::getAccountInformation)
.ordered(Comparator.comparing(RelatedAccounts::getCorrectOrder)) // blah blah, convert from ParallelFlux to Flux
}
其他服务
public Mono<AccountInformation> getAccountInformation(String accountNumber) {
WebClient webClient = WebClient.builder()
.baseUrl("http://myurl.com")
.build();
return webClient
.get()
.uri(uriBuilder -> uriBuilder
.queryParam("accountNumber", accountNumber)
.build()
).retrieve()
.bodyToMono(AccountInformation.class) // This doesn't work for us, we get a very complex object as a response and need to parse a few fields.
}
我在 Google 上花了一整天,但我没有看到其他人解析他们返回的响应,他们只是神奇地将其直接映射到完美创建的 class 上。我们没有那个选项,我们需要从响应主体中提取 accountName 并将其放入 AccountInformation 对象中。有人知道怎么做吗?
我的建议是继续使用 bodyToMono(AccountInformation.class)
,然后使用 Monos map and zip 映射到您的简单对象。
- 创建一个 class 来表示复杂的 AccountInformation,但只包含您需要的信息(不要包含您不需要的对象字段)。
- 使用您的其他服务(returns Mono)的确切代码
- 您可以使用
.zip
将您的简单对象与 AccountInformation 对象结合起来。 - 因此,您的简单对象只包含复杂对象所需的数据
例如
其他服务保持不变:
public Mono<AccountInformation> getAccountInformation(String accountNumber) {
WebClient webClient = WebClient.builder()
.baseUrl("http://myurl.com")
.build();
return webClient
.get()
.uri(uriBuilder -> uriBuilder
.queryParam("accountNumber", accountNumber)
.build()
).retrieve()
.bodyToMono(AccountInformation.class)
}
创建缺失的class(表示其他服务的结果),跳过所有您不需要的信息字段
import java.util.List;
public class AccountInformation {
private String infoYouWant;
private List<AccountDetails> otherNestedInfoYouWant;
// getters/setters skipped for easier read
}
将您的简单对象与来自其他服务的对象合并:
Mono<AccountInformation> accountInformation = someService.getAccountInformation("fillMeWithAccountNumber");
Mono<SimpleObject> simpleObjectWithAccountInformation = Mono.zip(simpleObject, accountInformation).map(objects -> {
SimpleObject simpleObject = objects.getT1();
simpleObject.setAccountNumber(objects.getT2().getInfoYouWant());
return simpleObject;
});
我们最终采用了一种非常丑陋但可扩展且有效的方法。在我们的其他服务中:
public Mono<AccountInformation> getAccountInformation(String accountNumber) {
WebClient webClient = WebClient.builder()
.baseUrl("http://myurl.com")
.build();
return webClient
.get()
.uri(uriBuilder -> uriBuilder
.queryParam("accountNumber", accountNumber)
.build()
).retrieve()
.bodyToMono(String.class) // just get the response as a JSON string
.map(jsonString -> {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonString);
// parse/ map out all your information here
return myCustomMappedObject;
})
}