Spring Boot 2.0のGAがリリースされて数週間経ちましたね。

2.0には大きな変更がいくつか含まれていますが、その1つがSpring 5で追加されたWebFluxによるリアクティブプログラミングです。

ちなみに、これからWebFluxに入門する人は、まず以下を見ると良いと思います。

WebFluxはHttpリクエストをハンドリングするサーバとして、以下の2つのパターンを選択する事ができます。

上でリンクを貼ったInfoQの記事の画像を見るとイメージしやすいと思います。

1.サーブレットコンテナ (Tomcat, Jetty or Undertow)
依存ライブラリに以下の両方を追加

  • spring-boot-starter-web
  • spring-boot-starter-webflux

この場合、Httpリクエストをサーブレットコンテナ (Tomcat, Jetty or Undertow)でハンドリングしつつ、
WebFluxの機能であるFunctional EndpointsWebClientも使えるようになります。

以下の資料もご参考までに

2.Netty
依存ライブラリに以下の1つだけを追加

  • spring-boot-starter-webflux


疑問

さて、ようやくここからが本題です。

サーバにNettyを選択した場合(依存ライブラリにspring-boot-starter-webfluxだけを追加した場合)、
Servletに依存しなくなるので javax.servlet.Filter などが使えなくなります。

今までFilterを使って書いていた共通的な処理はどう実装すれば良いんだろう?という疑問があったので調べてみました。

Filterでの共通処理、例えばこういうやつです。

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

@WebFilter
public class SampleFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // do something
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        try {
            chain.doFilter(request, response);
        } catch (Exception e) {
            // do something
        } finally {
            // do something
        }
    }

    @Override
    public void destroy() {
        // do something
    }
}

結論

Spring 5から、WebFlux用のorg.springframework.web.server.WebFilterというinterfaceが追加されているので、それを使えばOKです。

以下、所要時間を標準出力に表示する例です。

  1. まず、overrideしたfilterメソッドがリクエスト時に呼ばれます
  2. 次に、doOnSuccess、doOnError、doOnSuccessOrErrorあたりを設定しておけば、レスポンス時に呼ばれるようになってます。
    なお、doOnSuccess、doOnError、doOnSuccessOrErrorのそれぞれは必ず設定する必要はありません。

@Order は必要に応じてどうぞ。

import org.reactivestreams.Publisher;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SampleFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange).compose(this::filter);
    }

    private Publisher<Void> filter(Mono<Void> call) {
        System.out.println("start");

        long start = System.currentTimeMillis();
        return call.doOnSuccess(done -> success(start))
                   .doOnError(cause -> error(start))
                   .doOnSuccessOrError((done, cause) -> successOrError(start, cause));
    }

    private void success(long start) {
        long end = System.currentTimeMillis();

        System.out.println("success " + (end - start));
    }

    private void error(long start) {
        long end = System.currentTimeMillis();

        System.out.println("error " + (end - start));
    }

    private void successOrError(long start, Throwable cause) {
        long end = System.currentTimeMillis();

        if (cause == null) {
            System.out.println("success");
        } else {
            System.out.println("error");
        }

        System.out.println("finished " + (end - start));
    }
}


サンプルソース

今回のサンプルソースは以下に置いています。

なお、参考にしたソースは、Spring Boot Actuatorに含まれる MetricsWebFilterです。



今回使用したバージョン

  • Spring Boot 2.0.0.RELEASE
  • Spring 5.0.4.RELEASE