【翻譯】在Spring WebFlux中處理錯誤

程式設計師韓斌發表於2020-12-30

原文連結: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獲得。

相關文章