【翻譯】Reactor 第七篇 Spring WebFlux 怎麼進行異常處理

六七十三發表於2023-02-17

1 概覽

在本教程中,我們將透過一個實際示例瞭解Spring WebFlux專案中處理錯誤的各種策略。

我們還將指出使用一種策略比另一種策略更有利的地方,並在最後提供完整原始碼的連結。

2 開始示例程式碼

maven 設定和之前介紹 Spring WebFlux 的文章一樣,

對於我們的示例,我們將使用一個 RESTful 端點,它將使用者名稱作為查詢引數並返回“Hello username”作為結果。首先,讓我們建立一個路由函式,這個路由函式將 “/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 中包含或返回其(sayHello方法的返回)結果的方法。

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

最後,實現 sayHello 方法,實現很簡單,直接拼接 hello 和引數 username 即可。

private Mono<String> sayHello(ServerRequest request) {
    try {
        // 應該是 username
        return Mono.just("Hello, " + request.queryParam("username").get());
    } catch (Exception e) {
        return Mono.error(e);
    }
}

因此,只要我們的請求中帶了 username 引數,我們的請求就能正常返回。舉個例子:我們請求“/hello?username=Tonni”,類似請求,我們總是能正常返回。

然而,如果我們的請求不帶 username 引數,我們的請求就會丟擲異常了。下面,我們來看看 Spring 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)
        .bodyValue(s));
}

這裡,每當有問題的連線函式丟擲異常的時候,我們直接返回一個靜態結果:“Hello Stranger”。

3.2 onErrorResume 處理異常

有三種使用 onErrorResume 處理異常的方式:

  • 計算動態回撥值
  • 透過回撥函式執行其他分支
  • 捕獲、包裝並重新丟擲錯誤,例如,作為自定義業務異常

讓我們看看怎麼計算值:

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

這裡,每當 sayHello 丟擲異常的時候,我們返回一個 “Error + 異常資訊(e.getMessage())”。

接下來,我們看看當異常發生呼叫回撥函式:

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

這裡,每當 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 丟擲異常的時候,我們丟擲一個定製異常 NameRequiredException,message是 “username is required”。

全域性處理異常

目前為止,我們提供的所有示例都在方法級別上處理了錯誤處理。但是我們可以選擇在全域性層面處理異常。為此,我們只需要兩步:

  • 自定義一個全域性錯誤響應屬性
  • 實現全域性錯誤處理 handler

這樣我們程式丟擲的異常將會自動轉換成 HTTP 狀態和 JSON 錯誤體。我們只需要繼承 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;
    }

}

這裡,當異常丟擲是,我們想要返回 BAD_REQUEST 狀態碼和“username is required”錯誤資訊作為錯誤屬性的一部分。

然後,我們來實現全域性錯誤處理 handler。

為此,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));
    }
}

在這個例子中,我們設定 handler 的 order 為 -2。這是為了給它一個比預設 handler,也就是 DefaultErrorWebExceptionHandler 一個更高的優先順序,它設定的 order 為 -1。

errorAttributes 物件將是我們在 Web 異常處理程式的建構函式中傳遞的物件的精確副本。理想情況下,這應該是我們自定義的錯誤屬性類。

然後我們明確生命了,我們希望將所有的異常處理路由到 renderErrorResponse() 中。

最後,我們獲取了錯誤屬性並插入到服務端響應體中。

然後這會生成一個 JSON 響應,其中包含了錯誤的詳細資訊,HTTP 狀態、機器端的異常資訊等。對於瀏覽器端,它有一個 “white-label”錯誤處理程式,可以以 HTML 形式呈現相同的資料,當然這個頁面可以定製。

總結

在本文中,我們研究了在 Spring WebFlux 專案中處理異常的集中策略,並指出使用一個策略優於其他策略的地方。

原始碼在 github 上

原文:https://www.baeldung.com/spri...

相關文章