Things of interesting

プログラミングに関する技術ネタの備忘録

Spring Webfluxで例外ハンドリングを行う

やること

SpringWebflux導入にあたって例外ハンドリングの方法を調べて実装します。
SpringMVCだとControllerAdvice+ResponseEntityExceptionHandlerを使って共通の例外ハンドリングとかを実装していると思いますが、これらが使えなくなるので替わりの方法を探します。

WebfluxのAPI実装について

このブログの内容を参考にさせていただきました。
はじめてのSpring WebFlux (その1 - Spring WebFluxを試す) - BLOG.IK.AM

Webfluxでも@Controller使った実装が使えますが、他にRouter Functionsというモデルを使って実装できるようになっています。
今回、例外ハンドリングを実装するにあたってRouter FunctionsのFilter機能を使っているので、主にRouter Functionsを使った実装になっています。
@Controller使った共通的な例外ハンドリングの仕組みはみつからなかったので今回は言及しません。

事前準備

SPRING INITIALIZR を使ってテンプレートプロジェクトを作成します。
SpringBootは2.0系を選択して、ReactiveWebをDependenciesに入れておきます。

以下の環境で作業を行います。

  • Java9
  • SpringBoot2.0.0M6

Router Functionsクラスの実装

ハンドラクラスの作成

/parent/child1のGETメソッドと/parent/child2へのPOSTメソッドの2つのAPIを定義したハンドラクラスを作成します。
nest()を利用することでパスのネストを定義することができます。
コメントアウトしていますが、GETなどでクエリからパラメータを取得する場合はqueryParam()で取得できます。またPOSTなどでBodyからパラメータを取得する場合はbodyToMonoで任意のMonoクラスに変換できます。
GetSampleResult、PostSampleRequest は適当なPOJO、WebfluxServiceも適当なダミーのServiceクラスです。

WebfluxHandler.java

@Component
public class WebfluxHandler {

    @Autowired
    private WebfluxService webfluxService;

    public RouterFunction<ServerResponse> routes() {
        return nest(path("/parent"),
                route(GET("/child1"), this::getSmple)
                        .andRoute(POST("/child2"), this::postSample)
        );
    }

    private Mono<ServerResponse> getSample(ServerRequest req) {
        // int a = Integer.valueOf(req.queryParam("aaa").orElse("1"));
        // String b = req.queryParam("bbb").orElse("defaultValue");
        // return ok().body(Flux.just(webfluxService.getSample(a, b)), GetSampleResult.class);
        return ok().build();
    }

    private Mono<ServerResponse> postSample(ServerRequest req) {
        // PostSampleRequest param = req.bodyToMono(PostSampleRequest.class).block();
        // webfluxService.postSample(param.getName, param.getValue());
        return ok().build();
    }
}

WebfluxのConfigクラスでfilterを定義

先ほど作成したハンドラを呼び出すことでルーティング設定が有効になります。
併せて.filter()を呼び出すことでException発生時の挙動を定義しています。
routes().andRoute()を呼び出すことで複数ハンドラに対してfilterを設定することもできます。 WebfluxRoutesConfig.java

@Configuration
public class WebfluxRoutesConfig {
    @Bean
    public RouterFunction<ServerResponse> routes(WebfluxHandler webfluxHandler ) {
        return webfluxHandler.routes().filter((request, next) -> {
            try {
                return next.handle(request);
            } catch (Exception e) {
                log.error("An error occured", e);
                return ServerResponse.badRequest().build();
            }
        });
    }
}

動作確認

/gradlew bootRunでプロセスを起動して、上記のパスへのHTTPリクエストに対して期待した応答が返ればOKです。
内容確認したい場合はコメントアウトを外して任意のメッセージを返したりExceptionを発生させたりしてみてください。