反应性 Spring 引导:return 在地图完成之前评估
Reactive Spring Boot: return evaluates before map finshed
我正在使用反应式 WebClient 构建一个 API 与其他 2 个 API 进行通信。 API2 需要从 API1 获取信息,然后我的服务结合 returns 这两个信息。
资源:
@GetMapping("monoMedication/{medID}")
public Mono<Object> getMonoMedication(@PathVariable String medID) throws SSLException {
Mono<Login> Login =createWebClient()
.post()
.uri("URI_LOGIN_API1" )
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(body))
.retrieve()
.bodyToMono(Login.class);
return Login.map(login-> {
Mono<String> medicationBundles = null;
try {
medicationBundles = createWebClient()
.post()
.uri("URI_API1_GET_DATA")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject("Information"))
.header("Authorization", login.getSessionId())
.retrieve()
.bodyToMono(String.class);
} catch (SSLException e) {
e.printStackTrace();
}
return medicationBundles.map(bundles_string -> {
try {
List<Object> bundle_list = mapper.readValue(bundles_string, new TypeReference<List<Object>>(){});
bundle_list.forEach(bundle-> processBundle(bundle,medicationList));
return medicationList;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
})
})
}
函数:
List<String> medicationList = new ArrayList<>();
private void processBundle(Object bundle, List<String> medicationlist) {
//do something to get id from bundle
String ID = bundle.getID();
// if i add something to medicationList.add(ID) here, it is in the required return of my API
Mono<String> Medication =
webClientBuilder.build()
.get()
.uri("URI_API2_GET_DATA"+ID)
.retrieve()
.bodyToMono(String.class);
Medication.map(medication_ID -> {
//do something to get information from medication_ID
String info = medication_ID.getInfo();
//this Information comes after the required return
return medicationList.add(info+ID);
}).subscribe();
}
我的问题是,return 在所需的最后一张地图完成之前出现。我不知何故错过了一些东西。我尝试了不同的方法,例如then(), thenMany(), thenReturn() 在不同的位置。
有没有办法做到这一点?如果有一个可能已经完成的简单示例,那也会有所帮助!
很难在您的代码中跟进,因为您正在以非最佳实践方式混合和匹配反应式编程与命令式编程。
您的代码无法编译,您有一些奇怪的事情,例如 medID
从未被使用,变量从未被声明,例如 body
。所以我只是“按原样”使用了您的代码,我没有制作一个完整的示例,只是一个指南。
您应该选择走被动路线还是命令路线。我的部分回答会自以为是,如果以后有人抱怨,那就是免责声明。
首先,您在每个请求中都创建了多个 WebClients
在我看来,这是一种不好的做法。创建 WebClient
是一种昂贵的不必要的操作,因为您可以重复使用它们,因此您应该在启动期间声明您的 Web 客户端并 @Autowire
它们。
@Configuration
public class WebClientConfig {
@Bean
@Qualifier("WebClient1")
public WebClient createWebClient1() {
return WebClient.create( ... );
}
@Bean
@Qualifier("WebClient2")
public WebClient createWebClient2() {
return WebClient.create( ... );
}
@Bean
@Qualifier("WebClient3")
public WebClient createWebClient3() {
return WebClient.create( ... );
}
}
然后通过将它们自动连接到您的 class.
来使用它们
清理代码并将其分解为函数后,使用适当的 returns 我希望这能让您了解我将如何构建它。你的问题是你没有从你的函数中正确地 returning,并且你没有链接 returns。一旦你需要使用 subscribe
你通常就知道你做错了什么。
@RestController
public class FooBar {
private WebClient webClient1;
private WebClient webClient2;
private WebClient webClient3;
@Autowire
public Foobar(@Qualifier("WebClient1") WebClient webclient1, @Qualifier("WebClient2") WebClient webclient2, @Qualifier("WebClient3") WebClient webclient3) {
this.webClient1 = webClient1;
this.webClient2 = webClient2;
this.webClient3 = webClient3;
}
@GetMapping("monoMedication/{medID}")
public Mono<List<MedicationData>> getMonoMedication(@PathVariable String medID) {
return doLogin()
.flatMap(login -> {
return getMedicationBundles(login.getSessionId());
}).flatMap(medicationBundles -> {
return getMedicationData(medicationBundle.getId());
}).collectList();
}
private Mono<Login> doLogin() {
return webClient1
.post()
.uri("URI_LOGIN_API1")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(body)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Login.class);
}
private Flux<MedicationBundle> getMedicationBundles(String sessionId) {
return webClient2
.post()
.uri("URI_API1_GET_DATA")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue("Information")
.header("Authorization", sessionId)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToFlux(MedicationBundle.class);
}
private Mono<String> getMedicationData(String medicationId) {
return webClient3.get()
.uri(uriBuilder - > uriBuilder
.path("/URI_API2_GET_DATA/{medicationId}")
.build(medicationId))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(MedicationData.class);
}
}
我是徒手写的,没有任何内容 IDE 它更多的是向您展示您应该如何构建代码,希望这能给您一些指导。
响应式编程中的一些注意事项:
避免使用 try/catch 块,在响应式编程中,您通常 return a Mono.empty()
忽略错误,或者 return a Mono.error()
包含异常。
使用 flatMap
异步处理,map
同步处理,还有其他几个运算符,如 concatMap
和 flatMapSequential
保持顺序,解释这些本身就是一个单独的答案。
尽可能避免使用void
方法,总是尝试使用pure functions(避免在void函数中操作列表,你用C++中的指针而不是Java),您可以通过链接 .then()
运算符从反应函数 return Mono<Void>
。
尽可能利用类型系统,尽可能不要序列化到 Object
创建数据的对象表示并序列化到它。
除非您是数据的消费者,否则几乎从不订阅您的应用程序。发起调用的通常是 subscriber
,您的应用程序通常是 producer
,而调用客户端(Web 或其他服务)是 subscriber
。多个 subscribers
通常是一种代码味道。
始终尝试 return 并链接 return。我在 return 上面写的所有函数和 return 上的链,这就是所谓的 construction belt analogy
。我个人总是从在第一行写 return 语句开始每个函数,然后我开始写我的反应性东西。永远不要在没有 return 链接的情况下留下 Mono
或 Flux
,因为 Mono
或 Flux
永远不会得到 运行 如果没有一个订阅它。
将代码结构化为函数,编写函数是免费的:)
一些好的读物和视频:
Webflux official documentation
(我建议先阅读官方的 reactor 文档,这些文档可能很难理解,除非你非常了解 reactor)
来自 spring 一个的好视频,这个几乎解决了我上面写的所有内容。
Do's and don't in reactive programming
如前所述,这有点基于个人意见,但希望这能为您提供一些指导。
我正在使用反应式 WebClient 构建一个 API 与其他 2 个 API 进行通信。 API2 需要从 API1 获取信息,然后我的服务结合 returns 这两个信息。 资源:
@GetMapping("monoMedication/{medID}")
public Mono<Object> getMonoMedication(@PathVariable String medID) throws SSLException {
Mono<Login> Login =createWebClient()
.post()
.uri("URI_LOGIN_API1" )
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(body))
.retrieve()
.bodyToMono(Login.class);
return Login.map(login-> {
Mono<String> medicationBundles = null;
try {
medicationBundles = createWebClient()
.post()
.uri("URI_API1_GET_DATA")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject("Information"))
.header("Authorization", login.getSessionId())
.retrieve()
.bodyToMono(String.class);
} catch (SSLException e) {
e.printStackTrace();
}
return medicationBundles.map(bundles_string -> {
try {
List<Object> bundle_list = mapper.readValue(bundles_string, new TypeReference<List<Object>>(){});
bundle_list.forEach(bundle-> processBundle(bundle,medicationList));
return medicationList;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
})
})
}
函数:
List<String> medicationList = new ArrayList<>();
private void processBundle(Object bundle, List<String> medicationlist) {
//do something to get id from bundle
String ID = bundle.getID();
// if i add something to medicationList.add(ID) here, it is in the required return of my API
Mono<String> Medication =
webClientBuilder.build()
.get()
.uri("URI_API2_GET_DATA"+ID)
.retrieve()
.bodyToMono(String.class);
Medication.map(medication_ID -> {
//do something to get information from medication_ID
String info = medication_ID.getInfo();
//this Information comes after the required return
return medicationList.add(info+ID);
}).subscribe();
}
我的问题是,return 在所需的最后一张地图完成之前出现。我不知何故错过了一些东西。我尝试了不同的方法,例如then(), thenMany(), thenReturn() 在不同的位置。 有没有办法做到这一点?如果有一个可能已经完成的简单示例,那也会有所帮助!
很难在您的代码中跟进,因为您正在以非最佳实践方式混合和匹配反应式编程与命令式编程。
您的代码无法编译,您有一些奇怪的事情,例如 medID
从未被使用,变量从未被声明,例如 body
。所以我只是“按原样”使用了您的代码,我没有制作一个完整的示例,只是一个指南。
您应该选择走被动路线还是命令路线。我的部分回答会自以为是,如果以后有人抱怨,那就是免责声明。
首先,您在每个请求中都创建了多个 WebClients
在我看来,这是一种不好的做法。创建 WebClient
是一种昂贵的不必要的操作,因为您可以重复使用它们,因此您应该在启动期间声明您的 Web 客户端并 @Autowire
它们。
@Configuration
public class WebClientConfig {
@Bean
@Qualifier("WebClient1")
public WebClient createWebClient1() {
return WebClient.create( ... );
}
@Bean
@Qualifier("WebClient2")
public WebClient createWebClient2() {
return WebClient.create( ... );
}
@Bean
@Qualifier("WebClient3")
public WebClient createWebClient3() {
return WebClient.create( ... );
}
}
然后通过将它们自动连接到您的 class.
来使用它们清理代码并将其分解为函数后,使用适当的 returns 我希望这能让您了解我将如何构建它。你的问题是你没有从你的函数中正确地 returning,并且你没有链接 returns。一旦你需要使用 subscribe
你通常就知道你做错了什么。
@RestController
public class FooBar {
private WebClient webClient1;
private WebClient webClient2;
private WebClient webClient3;
@Autowire
public Foobar(@Qualifier("WebClient1") WebClient webclient1, @Qualifier("WebClient2") WebClient webclient2, @Qualifier("WebClient3") WebClient webclient3) {
this.webClient1 = webClient1;
this.webClient2 = webClient2;
this.webClient3 = webClient3;
}
@GetMapping("monoMedication/{medID}")
public Mono<List<MedicationData>> getMonoMedication(@PathVariable String medID) {
return doLogin()
.flatMap(login -> {
return getMedicationBundles(login.getSessionId());
}).flatMap(medicationBundles -> {
return getMedicationData(medicationBundle.getId());
}).collectList();
}
private Mono<Login> doLogin() {
return webClient1
.post()
.uri("URI_LOGIN_API1")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(body)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Login.class);
}
private Flux<MedicationBundle> getMedicationBundles(String sessionId) {
return webClient2
.post()
.uri("URI_API1_GET_DATA")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue("Information")
.header("Authorization", sessionId)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToFlux(MedicationBundle.class);
}
private Mono<String> getMedicationData(String medicationId) {
return webClient3.get()
.uri(uriBuilder - > uriBuilder
.path("/URI_API2_GET_DATA/{medicationId}")
.build(medicationId))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(MedicationData.class);
}
}
我是徒手写的,没有任何内容 IDE 它更多的是向您展示您应该如何构建代码,希望这能给您一些指导。
响应式编程中的一些注意事项:
避免使用 try/catch 块,在响应式编程中,您通常 return a
Mono.empty()
忽略错误,或者 return aMono.error()
包含异常。使用
flatMap
异步处理,map
同步处理,还有其他几个运算符,如concatMap
和flatMapSequential
保持顺序,解释这些本身就是一个单独的答案。尽可能避免使用
void
方法,总是尝试使用pure functions(避免在void函数中操作列表,你用C++中的指针而不是Java),您可以通过链接.then()
运算符从反应函数 returnMono<Void>
。尽可能利用类型系统,尽可能不要序列化到
Object
创建数据的对象表示并序列化到它。除非您是数据的消费者,否则几乎从不订阅您的应用程序。发起调用的通常是
subscriber
,您的应用程序通常是producer
,而调用客户端(Web 或其他服务)是subscriber
。多个subscribers
通常是一种代码味道。始终尝试 return 并链接 return。我在 return 上面写的所有函数和 return 上的链,这就是所谓的
construction belt analogy
。我个人总是从在第一行写 return 语句开始每个函数,然后我开始写我的反应性东西。永远不要在没有 return 链接的情况下留下Mono
或Flux
,因为Mono
或Flux
永远不会得到 运行 如果没有一个订阅它。将代码结构化为函数,编写函数是免费的:)
一些好的读物和视频:
Webflux official documentation (我建议先阅读官方的 reactor 文档,这些文档可能很难理解,除非你非常了解 reactor)
来自 spring 一个的好视频,这个几乎解决了我上面写的所有内容。 Do's and don't in reactive programming
如前所述,这有点基于个人意见,但希望这能为您提供一些指导。