WebFlux:只有一项到达后端

时间:2018-08-27 13:14:12

标签: spring-webflux

在后端即时通讯上做

@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();

导致后端再次根本没有调用的行为。

1 个答案:

答案 0 :(得分:1)

Reactor文档does say that nothing happens until you subscribe,但这并不意味着您应该订阅Spring WebFlux代码。

Spring WebFlux中应遵循以下规则:

  • 如果您需要以反应方式进行操作,则方法的返回类型应为MonoFlux
  • 在返回反应式错误的方法中,切勿调用blocksubscribetoIterable或任何其他自身不返回反应式类型的方法
  • 绝对不要在副作用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();
}

这样做不正确:

  • 调用subscribe将请求/响应的处理与该saveProduct操作分离。就像在其他执行程序中启动该处理一样。
  • 返回Mono.empty()表示Spring WebFlux您已经完成请求处理。因此,Spring WebFlux将关闭并清除请求/响应资源。但是您的saveProduct进程仍在运行,并且由于Spring WebFlux关闭并清除了该请求,因此无法从该请求中读取数据。

根据评论中的建议,您可以wrap blocking operations with Reactor(尽管不建议这样做,并且可能会遇到性能问题),并确保将所有操作都连接在单个反应式管道中。