WebFluxにおけるFilter
Spring Boot 2.0のGAがリリースされて数週間経ちましたね。
2.0には大きな変更がいくつか含まれていますが、その1つがSpring 5で追加されたWebFluxによるリアクティブプログラミングです。
ちなみに、これからWebFluxに入門する人は、まず以下を見ると良いと思います。
- InfoQ - Servlet and Reactive Stacks in Spring Framework 5
- SpringOne Platform 2017 - Servlet or Reactive Stacks: The Choice is Yours. Oh No…The Choice is Mine!
WebFluxはHttpリクエストをハンドリングするサーバとして、以下の2つのパターンを選択する事ができます。
上でリンクを貼ったInfoQの記事の画像を見るとイメージしやすいと思います。
1.サーブレットコンテナ (Tomcat, Jetty or Undertow)
依存ライブラリに以下の両方を追加
spring-boot-starter-web
spring-boot-starter-webflux
この場合、Httpリクエストをサーブレットコンテナ (Tomcat, Jetty or Undertow)でハンドリングしつつ、
WebFluxの機能であるFunctional EndpointsやWebClientも使えるようになります。
以下の資料もご参考までに
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です。
以下、所要時間を標準出力に表示する例です。
- まず、overrideしたfilterメソッドがリクエスト時に呼ばれます
- 次に、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