Handling Errors in Spring WebFlux – 在Spring WebFlux中处理错误

最后修改: 2018年 7月 17日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll look at various strategies available for handling errors in a Spring WebFlux project while walking through a practical example.

在本教程中,我们将探讨在Spring WebFlux项目中处理错误的各种策略,同时通过一个实际的例子。

We’ll also point out where it might be advantageous to use one strategy over another and provide a link to the full source code at the end.

我们还将指出在哪些地方使用一种策略可能比另一种策略更有优势,并在最后提供一个完整的源代码链接。

2. Setting Up the Example

2.设置实例

The Maven setup is the same as our previous article, which provides an introduction to Spring WebFlux.

Maven的设置与我们的上一篇文章相同,后者介绍了Spring WebFlux的情况。

For our example, we’ll use a RESTful endpoint that takes a username as a query parameter and returns “Hello username” as a result.

在我们的例子中,我们将使用一个RESTful端点,该端点接收一个用户名作为查询参数,并返回 “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);
    }

Next, we’ll define the handleRequest() method that calls the sayHello() method and finds a way of including/returning its result in the ServerResponse body:

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

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

Finally, the sayHello() method is a simple utility method that concatenates the “Hello” String and the username:

最后,sayHello()方法是一个简单的实用方法,将 “Hello”String和用户名连接起来。

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

So long as a username is present as part of our request, e.g., if the endpoint is called as “/hello?username=Tonni“, this endpoint will always function correctly.

只要用户名作为我们请求的一部分出现,例如,如果端点被调用为”/hello?username=Tonni“,这个端点就会一直正常工作。

However, if we call the same endpoint without specifying a username, e.g., “/hello“, it will throw an exception.

然而,如果我们在没有指定用户名的情况下调用同一个端点,例如,”/hello“,它将抛出一个异常。

Below, we’ll look at where and how we can reorganize our code to handle this exception in WebFlux.

下面,我们将看看在WebFlux中,我们可以在哪里以及如何重新组织我们的代码来处理这种异常。

3. Handling Errors at a Functional Level

3.在功能层面上处理错误

There are two key operators built into the Mono and Flux APIs to handle errors at a functional level.

MonoFlux API中内置了两个关键操作符,以在功能层面上处理错误。

Let’s briefly explore them and their usage.

让我们简单地探讨一下它们和它们的用法。

3.1. Handling Errors With onErrorReturn

3.1 用onErrorReturn处理错误

We can use onErrorReturn() to return a static default value whenever an error occurs:

我们可以使用onErrorReturn()来返回一个静态默认值,只要发生错误。

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

Here we’re returning a static “Hello Stranger” whenever the buggy concatenation function sayHello() throws an exception.

在这里,每当错误的连接函数sayHello()抛出一个异常时,我们就返回一个静态的 “Hello Stranger”。

3.2. Handling Errors With onErrorResume

3.2.用onErrorResume处理错误

There are three ways that we can use onErrorResume to handle errors:

我们有三种方法可以使用onErrorResume来处理错误。

  • Compute a dynamic fallback value
  • Execute an alternative path with a fallback method
  • Catch, wrap and re-throw an error, e.g., as a custom business exception

Let’s see how we can compute a value:

让我们看看如何计算一个值。

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

Here we’re returning a String consisting of the dynamically obtained error message appended to the string “Error” whenever sayHello() throws an exception.

在这里,只要sayHello()抛出一个异常,我们就会返回一个由动态获得的错误信息组成的字符串,并附加到字符串 “Error “上。

Next, let’s call a fallback method when an error occurs:

接下来,让我们在发生错误时调用一个回退方法

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

Here we’re calling the alternative method sayHelloFallback() whenever sayHello() throws an exception.

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

The final option using onErrorResume() is to catch, wrap and re-throw an error, e.g., as a NameRequiredException:

使用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);
}

Here we’re throwing a custom exception with the message “username is required” whenever sayHello() throws an exception.

在这里,只要sayHello()抛出一个异常,我们就会抛出一个自定义的异常,消息是 “用户名是必须的”。

4. Handling Errors at a Global Level

4.在全局层面处理错误

So far, all the examples we’ve presented have tackled error handling at a functional level.

到目前为止,我们介绍的所有例子都是在功能层面上解决错误处理问题。

However, we can opt to handle our WebFlux errors at a global level. To do this, we only need to take two steps:

然而,我们可以选择在全局层面上处理我们的WebFlux错误。要做到这一点,我们只需要采取两个步骤。

  • Customize the Global Error Response Attributes
  • Implement the Global Error Handler

The exception that our handler throws will be automatically translated to an HTTP status and a JSON error body.

我们的处理程序抛出的异常将被自动翻译成HTTP状态和JSON错误体。

To customize these, we can simply extend the DefaultErrorAttributes class and override its getErrorAttributes() method:

要定制这些,我们可以简单地扩展DefaultErrorAttributes并重写其getErrorAttributes()方法。

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

}

Here we want the status: BAD_REQUEST and the message “username is required” returned as part of the error attributes when an exception occurs.

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

Next, let’s implement the Global Error Handler.

接下来,让我们实现全局错误处理程序

For this, Spring provides a convenient AbstractErrorWebExceptionHandler class for us to extend and implement in handling global errors:

为此,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, 
         ErrorAttributeOptions.defaults());

       return ServerResponse.status(HttpStatus.BAD_REQUEST)
         .contentType(MediaType.APPLICATION_JSON)
         .body(BodyInserters.fromValue(errorPropertiesMap));
    }
}

In this example, we set the order of our global error handler to -2. This is to give it a higher priority than the DefaultErrorWebExceptionHandler, which is registered at @Order(-1).

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

The errorAttributes object will be the exact copy of the one that we pass in the Web Exception Handler’s constructor. This should ideally be our customized Error Attributes class.

errorAttributes对象将是我们在Web Exception Handler的构造函数中传递的对象的精确拷贝。这最好是我们定制的错误属性类。

Then we’re clearly stating that we want to route all error handling requests to the renderErrorResponse() method.

那么我们就明确指出,我们要将所有的错误处理请求路由到renderErrorResponse()方法。

Finally, we get the error attributes and insert them inside a server response body.

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

This then produces a JSON response with details of the error, the HTTP status, and the exception message for machine clients. For browser clients, it has a “white-label” error handler that renders the same data in HTML format. This can be customized, of course.

然后产生一个JSON响应,包含错误的细节、HTTP状态和机器客户端的异常信息。对于浏览器客户端,它有一个 “白标 “错误处理程序,以HTML格式显示相同的数据。当然,这也是可以定制的。

5. Conclusion

5.结论

In this article, we looked at various strategies available for handling errors in a Spring WebFlux project and pointed out where it might be advantageous to use one strategy over another.

在这篇文章中,我们研究了在Spring WebFlux项目中处理错误的各种策略,并指出了使用一种策略比另一种策略更有优势的地方。

As promised, the full source code that accompanies the article is available over on GitHub.

正如所承诺的那样,伴随着这篇文章的完整源代码可以在GitHub上获得。