原文:http://www.baeldung.com/spring-webflux-errors

1. 概览

在本教程中,我们通过一个实际的例子来看一下可用于处理Spring WebFlux项目中的错误的各种策略

我们还将指出在哪种情况下使用一种策略会比另外一种好,在本文最后将提供所有源码的下载地址。

2. 配置实例

上一篇文章 previous article 中已经提到了maven的配置, 并对 Spring Webflux做了简单的介绍。

在这个例子中,我们为一个 RESTful 端点加上一个名为 username 的查询参数,并以“Hello username”作为结果返回。

First, let’s create a router function that routes the /hello request to a method named handleRequest in the passed-in handler:

首先,让我们创建一个路由器函数,将/hello请求路由名为handleRequest的方法中:

@Bean
public RouterFunction<ServerResponse> routeRequest(Handler handler) {
    return RouterFunctions.route(RequestPredicates.GET("/hello")
      .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), 
        handler::handleRequest);
    }

接下来,我们将定义handleRequest()方法,该方法调用sayHello()方法并在ServerResponse主体中包含/返回其结果的方法:

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return
      //...
        sayHello(request)
      //...
}

最后,sayHello()是一个简单的实用工具方法,它将“Hello”和 username 连接起来返回。

private Mono<String> sayHello(ServerRequest request) {
    //...
    return Mono.just("Hello, " + request.queryParam("name").get());
    //...
}

只要用 username 作为我们请求的一部分存在,例如使用“/hello?username=Tonni”访问,我们的端点就可以正确运行。

然而,如果我们调用"/hello"的时候没有使用 username 这个参数,它会抛出一个异常。

下面,我们将看看我们在何处如何重新组织我们的代码才能在WebFlux中处理此异常。

3. 在函数级别处理错误

Mono和Flux API内置了两个关键操作符,用于处理功能级别的错误。

让我们简要地探讨它们及其用法。

3.1. 使用 onErrorReturn

当出现错误时,我们可以使用 onErrorReturn()来返回一个静态的默认值。

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return sayHello(request)
      .onErrorReturn("Hello Stranger")
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s));
}

sayHello()抛出异常时,函数就会默认返回"Hello Stranger"。

3.2. 使用onErrorResume

使用onErrorResume处理错误有三种方式:

  • 计算动态返回值
  • 使用fallback方法 跳转到备份路径
  • 捕获,包装和重新抛出错误,例如 作为自定义业务异常

让我们看看怎么杨计算一个值:

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
          .syncBody(s))
        .onErrorResume(e -> Mono.just("Error " + e.getMessage())
          .flatMap(s -> ServerResponse.ok()
            .contentType(MediaType.TEXT_PLAIN)
            .syncBody(s)));
}

在这里,每当sayHello()抛出异常时,我们将返回一个字符串,该字符串由附加到字符串“Error”的动态获取的错误消息组成。

接下来,当错误发生时我们调用 fallback 方法:

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s))
      .onErrorResume(e -> sayHelloFallback()
      .flatMap(s ->; ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s)));
}

在这里,只要sayHello()抛出异常,我们就会调用替代方法sayHelloFallback()。

使用onErrorResume()的最后一个选项是捕获,包装和重新抛出错误,例如 作为NameRequiredException

public Mono<ServerResponse> handleRequest(ServerRequest request) {
    return ServerResponse.ok()
      .body(sayHello(request)
      .onErrorResume(e -> Mono.error(new NameRequiredException(
        HttpStatus.BAD_REQUEST, 
        "username is required", e))), String.class);
}

在这里,只要sayHello()抛出异常,我们就会抛出一个自定义异常,并带有消息:"username is required"。

4. 全局级别的错误处理

到目前为止,我们提供的所有示例都在函数级别上处理了错误处理。

但是,我们可以选择在全局范围内处理我们的WebFlux错误。 要做到这一点,我们只需要采取两个步骤:

  • 自定义全局错误响应属性
  • 实现全局错误处理程序

我们的处理程序抛出的异常将被自动转换为HTTP状态和JSON错误正文。 要自定义这些,我们可以简单地扩展DefaultErrorAttributes类并覆盖其getErrorAttributes()方法:

public class GlobalErrorAttributes extends DefaultErrorAttributes{
     
    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, 
      boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(
          request, includeStackTrace);
        map.put("status", HttpStatus.BAD_REQUEST);
        map.put("message", "username is required");
        return map;
    }
 
}

在这里,我们希望状态:BAD_REQUEST和消息:"username is required"在发生异常时作为错误属性的一部分返回。

接下来,让我们实现全局错误处理程序。 为此,Spring提供了一个方便的AbstractErrorWebExceptionHandler类,供我们在处理全局错误时进行扩展和实现:

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends
    AbstractErrorWebExceptionHandler {
 
    // constructors
 
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(
      ErrorAttributes errorAttributes) {
 
        return RouterFunctions.route(
          RequestPredicates.all(), this::renderErrorResponse);
    }
 
    private Mono<ServerResponse> renderErrorResponse(
       ServerRequest request) {
 
       Map<String, Object> errorPropertiesMap = getErrorAttributes(request, false);
 
       return ServerResponse.status(HttpStatus.BAD_REQUEST)
         .contentType(MediaType.APPLICATION_JSON_UTF8)
         .body(BodyInserters.fromObject(errorPropertiesMap));
    }
}

在这个例子中,我们将全局错误处理程序的顺序设置为-2。 这是为了给它一个比在@Order(-1)注册的DefaultErrorWebExceptionHandler更高的优先级。

errorAttributes对象将是我们在Web异常处理程序的构造函数中传递的副本的精确副本。 理想情况下,这应该是我们自定义的Error Attributes类。

然后,我们清楚地说明我们想要将所有错误处理请求路由到renderErrorResponse()方法。

最后,我们获取错误属性并将它们插入服务器响应主体中。

然后,它会生成一个JSON响应,其中包含错误,HTTP状态和计算机客户端的异常消息的详细信息。 对于浏览器客户端,它有一个“whitelabel”错误处理程序,它以HTML格式呈现相同的数据。 当然,这可以是定制的。

5. 结尾

在本文中,我们研究了可用于处理Spring WebFlux项目中的错误的各种策略,并指出了使用一种策略而不是另一种策略的优势。

正如所承诺的那样,本文附带的完整源代码可以在 GitHub获得。