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

Posted

技术标签:

【中文标题】如何使用 Spring Boot 对 WebFlux 进行异常处理?【英文标题】:How to do Exception Handling for WebFlux using Springboot? 【发布时间】:2020-01-20 11:50:23 【问题描述】:

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

示例代码: (引自-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 或其他?

我试过了:

    将 .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))))
    添加异常处理程序 - 但我完全没有控制器,所以这似乎不起作用。
@ExceptionHandler(WebClientException.class)
    public Exception handlerWebClientException(WebClientException webClientException) 
        return new Data4xxException("Testing", webClientException);
    
    添加了一个包含 ControllerAdvice 和 ExceptionHandler 的类
@ControllerAdvice
public class WebFluxExceptionHandler 

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

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

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

如何让zip方法抛出异常而不返回null?

【问题讨论】:

什么是 Data4xxException?它是自定义异常吗?你说你没有控制器那么你如何调用你的代码? 是的,抱歉忘了提,这是一个自定义异常。我的代码是一个 springboot 应用程序,它使用来自 JMS 的数据,所以我有监听器。 【参考方案1】:

你问的时候不是很清楚

“如何让zip方法抛出异常而不返回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 )

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

【讨论】:

谢谢,虽然这给了我一个提示......正如我提到的,我想抛出一个异常,以便在需要时其他一些类可以处理该异常,否则我可以丢弃整个事务。无论如何,经过多次试错,我想出了一个办法。【参考方案2】:

做到这一点的方法是通过以下方式使用onErrorMap:

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

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

【讨论】:

【参考方案3】:

WebClient 中的retrieve() 方法抛出WebClientResponseException 每当收到状态码为 4xx 或 5xx 的响应时。

与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/

【讨论】:

以上是关于如何使用 Spring Boot 对 WebFlux 进行异常处理?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Spring Boot 配置属性对属性进行分组

如何使用 spring security 和 spring boot 对 Google 用户进行身份验证,将 mongoDB 作为存储库?

如何使用 Gradle Kotlin DSL 对 Spring Boot 应用程序进行 Dockerize

如何使用 power mock 对 Spring Boot Rest 控制器和异常处理程序进行单元测试

如何使用spring dev工具在docker中自动重新加载spring boot应用程序

如何对 Docker 容器中运行的 Spring Boot 应用程序进行健康检查?