如何使用 Spring Boot 对 WebFlux 进行异常处理?

How to do Exception Handling for WebFlux using Springboot?

我有3个微服务应用。我正在尝试使用反应包中的 webclient 进行 2 get 异步调用,然后在收到响应时将它们组合起来。

示例代码: (引用自 - https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/web-reactive.html#webflux-client-synchronous

Mono<Person> personMono = client.get().uri("/person/{id}", personId)
        .retrieve().bodyToMono(Person.class);

Mono<List<Hobby>> hobbiesMono = client.get().uri("/person/{id}/hobbies", personId)
        .retrieve().bodyToFlux(Hobby.class).collectList();

Map<String, Object> data = Mono.zip(personMono, hobbiesMono, (person, hobbies) -> {
            Map<String, String> map = new LinkedHashMap<>();
            map.put("person", personName);
            map.put("hobbies", hobbies);
            return map;
        })
        .block();

我的问题是如何向 get 调用添加异常处理?

如何检查我收到的是 404、204 还是其他?

我试过:

  1. 将 .onStatus() 添加到 GET 调用
    .onStatus(HttpStatus::is4xxClientError, clientResponse ->
             Mono.error(new Data4xxException(String.format(
                "Could not GET data with id: %s from another app, due to error: 
                 %s", key, clientResponse))))
    .onStatus(HttpStatus::is5xxServerError, clientResponse ->
          Mono.error(new Data5xxException(
              String.format("For Data %s, Error Occurred: %s", key, clientResponse))))
  1. 添加异常处理程序 - 但我确实没有控制器,所以这似乎不起作用。
@ExceptionHandler(WebClientException.class)
    public Exception handlerWebClientException(WebClientException webClientException) {
        return new Data4xxException("Testing", webClientException);
    }
  1. 添加了一个 class,其中包含 ControllerAdvice 和 ExceptionHandler
@ControllerAdvice
public class WebFluxExceptionHandler {

    @ExceptionHandler(WebClientException.class)
    public Exception handlerWebClientException(WebClientException webClientException) {
        return new Data4xxException("Testing", webClientException);
    }
}

但我没有在 spring-boot 日志中看到它们。

Mono.zip.block() 方法只是 return 为 null,实际上并没有抛出任何异常。

如何让 zip 方法抛出异常而不是 return null?

你问的不是很清楚

“如何让 zip 方法抛出异常而不是 return null?”

在 WebFlux 中,您通常不会抛出异常,而是传播异常然后处理它们。为什么?因为我们正在处理数据流,如果你抛出异常,流结束,客户端断开连接,事件链停止。

我们仍然希望维护数据流并处理流经的不良数据。

您可以使用 doOnError 方法处理错误。

.onStatus(HttpStatus::is4xxClientError, clientResponse ->
         Mono.error(new Data4xxException(String.format(
            "Could not GET data with id: %s from another app, due to error: 
             %s", key, clientResponse))))

Mono.zip( .. ).doOnError( //Handle your error, log or whatever )

如果你想做一些更具体的事情,你必须用你希望如何处理你的错误来更新你的问题。

方法是按以下方式使用 onErrorMap:

Mono<Person> personMono = client.get()
.uri("/person/{id}", personId)
.retrieve()
.bodyToMono(Person.class)
.onErrorMap((Throwable error) -> error);

onErrorMap 将使 Mono 在 Zip 阻塞时实际抛出错误,终止 zip 并让 spring 或您想要处理异常的任何其他 class。

The retrieve() method in WebClient throws a WebClientResponseException whenever a response with status code 4xx or 5xx is received.

与 retrieve() 方法不同,exchange() 方法在 4xx 或 5xx 响应的情况下不会抛出异常。您需要自己检查状态码并按照您想要的方式进行处理。

   Mono<Object> result = webClient.get().uri(URL).exchange().log().flatMap(entity -> {
        HttpStatus statusCode = entity.statusCode();
        if (statusCode.is4xxClientError() || statusCode.is5xxServerError())
        {
            return Mono.error(new Exception(statusCode.toString()));
        }
        return Mono.just(entity);
    }).flatMap(clientResponse -> clientResponse.bodyToMono(JSONObject.class))

参考: https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/