在后端即时通讯上做
@PostMapping(path = "/products", consumes = MediaType.APPLICATION_STREAM_JSON_VALUE)
public void saveProducts(@Valid @RequestBody Flux<Product> products) {
products.subscribe(product -> log.info("product: " + product.toString()));
}
在前端即时消息上,我使用以下命令调用
:this.targetWebClient
.post()
.uri(productUri)
.accept(MediaType.APPLICATION_STREAM_JSON)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(this.sourceWebClient
.get()
.uri(uriBuilder -> uriBuilder.path(this.sourceEndpoint + "/id")
.queryParam("date", date)
.build())
.accept(MediaType.APPLICATION_STREAM_JSON)
.retrieve()
.bodyToFlux(Product.class), Product.class)
.exchange()
.subscribe();
现在发生的是,我有472种产品需要保存,但实际上只有其中一种正在保存。该流在第一个流之后关闭,我找不到原因。
如果我这样做:
...
.retrieve()
.bodyToMono(Void.class);
相反,请求甚至没有到达后端。
我还尝试了固定元素的数量:
.body(Flux.just(new Product("123"), new Product("321")...
只有第一个到达。
编辑
我更改了代码:
@PostMapping(path = "/products", consumes =
MediaType.APPLICATION_STREAM_JSON_VALUE)
public Mono<Void> saveProducts(@Valid @RequestBody Flux<Product> products) {
products.subscribe(product -> this.service.saveProduct(product));
return Mono.empty();
}
和:
this.targetWebClient
.post()
.uri(productUri)
.accept(MediaType.APPLICATION_STREAM_JSON)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(this.sourceWebClient
.get()
.uri(uriBuilder -> uriBuilder.path(this.sourceEndpoint + "/id")
.queryParam("date", date)
.build())
.accept(MediaType.APPLICATION_STREAM_JSON)
.retrieve()
.bodyToFlux(Product.class), Product.class)
.exchange()
.block();
这导致一种产品被保存两次的行为(因为后端端点被两次调用),但又仅保存了一项。而且我们在前端也遇到了错误:
IOException: Connection reset by peer
相同于:
...
.retrieve()
.bodyToMono(Void.class)
.subscribe();
执行以下操作:
this.targetWebClient
.post()
.uri(productUri)
.accept(MediaType.APPLICATION_STREAM_JSON)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(this.sourceWebClient
.get()
.uri(uriBuilder -> uriBuilder.path(this.sourceEndpoint + "/id")
.queryParam("date", date)
.build())
.accept(MediaType.APPLICATION_STREAM_JSON)
.retrieve()
.bodyToFlux(Product.class), Product.class)
.retrieve();
导致后端再次根本没有调用的行为。
答案 0 :(得分:1)
Reactor文档does say that nothing happens until you subscribe,但这并不意味着您应该订阅Spring WebFlux代码。
Spring WebFlux中应遵循以下规则:
Mono
或Flux
block
或subscribe
,toIterable
或任何其他自身不返回反应式类型的方法DoOnXYZ
运算符中进行与I / O相关的操作,因为它们不是这样做的目的,并且这将在运行时引起问题在您的情况下,您的后端应使用反应性存储库来保存数据,并且应类似于:
@PostMapping(path = "/products", consumes = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Mono<Void> saveProducts(@Valid @RequestBody Flux<Product> products) {
return productRepository.saveAll(products).then();
}
在这种情况下,Mono<Void>
返回类型意味着您的控制器将不返回任何内容作为响应主体,但在处理完请求后仍会发出信号。这可能可以解释您为什么会看到这种行为-到控制器完成处理请求时,所有产品都没有保存在数据库中。
此外,请记住上述规则。根据使用targetWebClient
的位置,可能无法解决方案,请在其上调用.subscribe();
。如果这是一个返回void
的测试方法,则可能要在其上调用block
并获取结果以测试其断言。如果这是一种组件方法,则您可能应该返回Publisher
类型作为返回值。
编辑:
@PostMapping(path = "/products", consumes =
MediaType.APPLICATION_STREAM_JSON_VALUE)
public Mono<Void> saveProducts(@Valid @RequestBody Flux<Product> products) {
products.subscribe(product -> this.service.saveProduct(product));
return Mono.empty();
}
这样做不正确:
saveProduct
操作分离。就像在其他执行程序中启动该处理一样。Mono.empty()
表示Spring WebFlux您已经完成请求处理。因此,Spring WebFlux将关闭并清除请求/响应资源。但是您的saveProduct
进程仍在运行,并且由于Spring WebFlux关闭并清除了该请求,因此无法从该请求中读取数据。根据评论中的建议,您可以wrap blocking operations with Reactor(尽管不建议这样做,并且可能会遇到性能问题),并确保将所有操作都连接在单个反应式管道中。