Spring WebFluxでのエラー処理

Spring WebFluxでのエラー処理

1. 概要

このチュートリアルでは、実際の例を歩きながらwe’ll look at various strategies available for handling errors in a Spring WebFlux projectを実行します。

また、ある戦略を別の戦略よりも使用することが有利な場合があることを指摘し、最後に完全なソースコードへのリンクを提供します。

2. サンプルのセットアップ

Mavenのセットアップは、Spring Webfluxの概要を提供するprevious articleと同じです。

この例では、結果としてwe’ll usea RESTful endpoint that takes a username as a query parameter and returns “Hello username”になります。

まず、渡されたハンドラーで/helloリクエストをhandleRequestという名前のメソッドにルーティングするルーター関数を作成しましょう。

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

次に、sayHello()メソッドを呼び出し、その結果をServerResponse本体に含める/返す方法を見つけるhandleRequest()メソッドを定義します。

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

最後に、sayHello()メソッドは、「Hello」Stringとユーザー名を連結する単純なユーティリティメソッドです。

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

ユーザー名がリクエストの一部として存在する限り、例えば エンドポイントが“/hello?username=Tonni "として呼び出された場合、このエンドポイントは常に正しく機能します。

ただし、if we call the same endpoint without specifying a username e.g. “/hello”, it will throw an exception.

以下では、WebFluxでこの例外を処理するために、コードをどこでどのように再編成できるかを見ていきます。

3. 機能レベルでのエラーの処理

機能レベルでエラーを処理するために、MonoおよびFluxAPIに組み込まれている2つの主要な演算子があります。

それらとその使用法について簡単に説明しましょう。

3.1. onErrorReturnでのエラーの処理

エラーが発生するたびにWe can use onErrorReturn() to return a static default value

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

ここでは、バグのある連結関数sayHello()が例外をスローするたびに、静的な「HelloStranger」を返します。

3.2. onErrorResumeでのエラーの処理

onErrorResumeを使用してエラーを処理する方法は3つあります。

  • 動的フォールバック値を計算する

  • 代替方法で代替パスを実行する

  • エラーをキャッチ、ラップ、再スローします。 カスタムビジネス例外として

値を計算する方法を見てみましょう。

public Mono 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」に追加された文字列を返します。

次に、call a fallback method when an error occurs

public Mono 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()を使用する最後のオプションは、catch, wrap, and re-throw an errorです。 NameRequiredException:として

public Mono 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()が例外をスローするたびに、「ユーザー名が必要です」というメッセージとともにカスタム例外をスローしています。

4. グローバルレベルでのエラーの処理

これまでに示したすべての例は、機能レベルでのエラー処理に取り組んできました。

We can, however, opt to handle our WebFlux errors at a global level.これを行うには、次の2つの手順を実行するだけです。

  • グローバルエラー応答属性をカスタマイズする

  • グローバルエラーハンドラーを実装する

ハンドラーがスローする例外は、HTTPステータスとJSONエラー本文に自動的に変換されます。 これらをカスタマイズするには、単にextend the DefaultErrorAttributes classを実行し、そのgetErrorAttributes()メソッドをオーバーライドします。

public class GlobalErrorAttributes extends DefaultErrorAttributes{

    @Override
    public Map getErrorAttributes(ServerRequest request,
      boolean includeStackTrace) {
        Map map = super.getErrorAttributes(
          request, includeStackTrace);
        map.put("status", HttpStatus.BAD_REQUEST);
        map.put("message", "username is required");
        return map;
    }

}

ここでは、ステータス:BAD_REQUESTとメッセージ:“username is required”が、例外が発生したときにエラー属性の一部として返されるようにします。

次に、implement the Global Error Handlerをしましょう。 このために、Springは、グローバルエラーの処理で拡張および実装するための便利なAbstractErrorWebExceptionHandlerクラスを提供します。

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends
    AbstractErrorWebExceptionHandler {

    // constructors

    @Override
    protected RouterFunction getRoutingFunction(
      ErrorAttributes errorAttributes) {

        return RouterFunctions.route(
          RequestPredicates.all(), this::renderErrorResponse);
    }

    private Mono renderErrorResponse(
       ServerRequest request) {

       Map errorPropertiesMap = getErrorAttributes(request, false);

       return ServerResponse.status(HttpStatus.BAD_REQUEST)
         .contentType(MediaType.APPLICATION_JSON_UTF8)
         .body(BodyInserters.fromObject(errorPropertiesMap));
    }
}

この例では、グローバルエラーハンドラの順序を-2に設定します。 これは、@Order(-1).に登録されているgive it a higher priority than the DefaultErrorWebExceptionHandlerに対するものです。

errorAttributesオブジェクトは、Web例外ハンドラーのコンストラクターで渡すオブジェクトの正確なコピーになります。 これは、カスタマイズされたエラー属性クラスであることが理想的です。

次に、すべてのエラー処理リクエストをrenderErrorResponse()メソッドにルーティングすることを明確に示しています。

最後に、エラー属性を取得し、サーバーの応答本文に挿入します。

これにより、エラーの詳細、HTTPステータス、およびマシンクライアントの例外メッセージを含むJSON応答が生成されます。 ブラウザクライアントには、同じデータをHTML形式で表示する「ホワイトラベル」エラーハンドラがあります。 もちろん、これはカスタマイズできます。

5. 結論

この記事では、Spring WebFluxプロジェクトでエラーを処理するために利用可能なさまざまな戦略を検討し、ある戦略を別の戦略よりも使用する方が有利な場合があることを指摘しました。

約束通り、記事に付属する完全なソースコードはover on GitHubで入手できます。