はじめに
- 今回はSpring5から追加されたSpring WebFluxについて簡単に説明します
- 今回のゴールはWebFluxの概念的なところを理解して、Hello Worldするところまでとします
- RequestParameterとかRequestBodyとかについては別記事でまとめます(多分...)
背景
- 1年くらい前からWebFluxの存在は知っていた(Spring Fest2018あたりで谷本さんの話は聞きました)
- ただ、必要に迫られることがなかったので特に勉強してなかった
- しかし、今回APIをWebFluxで実装することになり、ちゃんと勉強しようと思った次第です
WebFluxとは?
- 2017年8月に一般リリースされたSpring5から追加された機能
- Spring5の最大の特徴となっているのが、リアクティブ・プログラミング・モデル
- 非同期であり、ノンブロッキング型であること
- 少ないスレッド数で実行され、垂直スケーリングが可能
TomcatとNetty
- 今まで、Spring Boot(WebMVC)を起動すると、Tomcatが立ち上がっていましたが、WebFluxで起動すると、Nettyが立ち上がるようになる
Tomcat
- Apache Tomcatのこと
- サーブレットやJSPを実行することができるオープンソースのサーブレットコンテナ
- Catalina(Servlet コンテナ), Coyote(HTTPサーバー),Jasperで構成される
Netty
- 元々はJBossによって開発されていたが、現在はNetty Project Communityによって維持・開発されている
- イベントドリブンな非同期通信を行うアプリケーションを開発するためのフレームワーク
実装してみる
- 説明はそこそこに実装をしてみます
用意したもの
- Java11
- Spring Boot2.1.8
- IntelliJ IDEA
Controllerモデル
- 早速実装してみます
- WebFluxでAPIを実装する方法は大別すると、アノテーションベースで実装する(Controllerモデル)方法と関数型っぽく書く方法(Router Functionsモデル)があります
- 前者はSpring WebMVCで実装する方法とほとんど同じなので、Spring WebMVCでAPIを実装していた人にとってはかなり馴染み深いものになっていると思われます
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}
- starter-webfluxをbuild.gradleに追加します
SampleController.java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class SampleController {
@GetMapping("/")
Mono<String> hello() {
return Mono.just("Hello World");
}
}
- WebMVCで実装していたノリで、戻り値をMonoにします
- ReactorクラスにはMono型とFlux型があり、リアクティブ・ストリーム仕様に従った Publisher インターフェースを実装します
- Mono オブジェクトは 1 個の要素のストリームを処理し、Flux は N 個の要素のストリームを処理します
SampleController.java
@RestController
public class SampleController {
@GetMapping("/2")
Flux<String> hello2() {
return Flux.just("hello", "world");
}
@GetMapping("/2")
Flux<String> hello3(@RequestParam String name) {
return Flux.just("hello", "world:", name);
}
}
- FluxにすればN個の要素を処理することができます
- また、今までのようにアノテーションベースでパラメータなども取得できるので、MVCと同じノリでコーディングすることができます
Router Functionsモデル
- では次に、Spring5の新しい関数型の手法で書いてみます
- RouterFunctionをBean定義すれば、Router Functionsのルーティング定義とみなしてくれるそうです
SampleController.java
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
@Component
public class SampleController {
@Bean
RouterFunction<ServerResponse> routes() {
return route(GET("/"), req -> ok().body(Flux.just("Hello", "World!"), String.class));
}
}
- 最初、ど忘れしてたのですが、対象のクラスをコンポーネントスキャンの対象にしないとBean登録されないので(当たり前の話ですが...)@SpringBootApplicationが書かれてるクラスに実装するか、任意のアノテーションをつけるようにしてください
- ちなみに、今はRouterFunctionの中で、レスポンスを返却してしまっていますが、ここは他のメソッド/クラスに切り出すことは可能です
SampleController.java
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
@Component
public class SampleController {
@Bean
public RouterFunction<ServerResponse> routes() {
return route(GET("/"), this::hello);
}
Mono<ServerResponse> hello(ServerRequest req) {
return ok().body(Flux.just("Hello", "World!"), String.class);
}
}
- 肝なのは、RouterFunctionをComponentスキャン対象クラスの中でBean登録してあげることだけでした
- ちなみに、パスパラメータは↓みたいな感じでとることができます
SampleController.java
@Component
public class SampleController {
@Bean
public RouterFunction<ServerResponse> routes() {
return route(GET("/{name}"), this::hello);
}
Mono<ServerResponse> hello(ServerRequest req) {
return ok().body(Flux.just("Hello", "World!", req.pathVariable("name")), String.class);
}
}
- リクエストパラメータだとこんな感じ
- queryParamの戻り値がoptionalなので、そこだけハンドリングが必要そうです
SampleController.java
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
@Component
public class SampleController {
@Bean
public RouterFunction<ServerResponse> routes() {
return route(GET("/"), this::hello);
}
Mono<ServerResponse> hello(ServerRequest req) {
return ok().body(Flux.just("Hello", "World!", req.queryParam("name").orElse("no name")), String.class);
}
}
まとめ
- 今更ながらWebFluxに入門できた
- 個人的な所感としては、アノテーションベースでイメージを掴んでから、Router Functionsをやってみるのもいいのかと。
- ControllerモデルとRouter Functionsモデルの使い分けについてははっきりとしたことがわからなかった
ただ、Router Functionsが追加されてるクラスとかを見ると、WebFluxをやる上ではRouter Functionsを使用するのが良いのかなという所感- まきさんのブログをみていた感じ、
Router Functionsの場合、アノテーションの読み込み(= Reflection処理)がないため、原理的には@Controllerよりも若干速いはずです
との記載があったので、WebFluxを使用する場面というのが、ある程度パフォーマンスを求められる場面が多いので、パフォーマンスを意識するという観点からもRouter Functionsを使用する方が望ましいのかと
- あくまでも個人の所感なので、詳しい方に説明いただきたい...(小声)
[以下追記 2019/09/29]
コメント欄にて、以下の通り教えていただきました。
プログラミングモデルの違いと比べると、微々たるもんなのでパフォーマンス起因で選ばないほうが良いと思います。
Functionalなアプローチが好きかどうか。できることで言うとアノテーションモデルの方が先に実装されていると思います。
ちなみにMVCの方でも5.2からはFunctionalなプログラミングモデルを選択できます。
とのことです。ですので、パフォーマンスで選択するのではなく、あくまでもアプローチの仕方で選択して良いみたいです。