Spring WebFluxは、Spring 5で導入された新しいモジュールです。Spring WebFluxは、Springフレームワークにおけるリアクティブプログラミングモデルへの第一歩です。
Springリアクティブプログラミング
リアクティブプログラミングモデルが初めての場合は、以下の記事を参照してリアクティブプログラミングについて学ぶことを強くお勧めします。
Spring 5が初めての場合は、Spring 5の特徴をご覧ください。
Spring WebFlux
Spring WebFluxは、Spring MVCモジュールの代替です。Spring WebFluxは、イベントループ実行モデル上に構築された完全非同期および非ブロッキングなアプリケーションを作成するために使用されます。以下の図は、Spring公式ドキュメンテーションからのSpring WebFluxとSpring Web MVCの比較についての洞察を提供しています。もし、非ブロッキングなリアクティブモデルでWebアプリケーションやREST Webサービスを開発したい場合は、Spring WebFluxを検討してみてください。Spring WebFluxは、Tomcat、Jetty、Servlet 3.1+コンテナだけでなく、NettyやUndertowなどの非Servletランタイムでもサポートされています。Spring WebFluxは、Project Reactor上に構築されています。Project Reactorは、Reactive Streams仕様の実装です。Reactorは、2つのタイプを提供します:
- Mono:Publisherを実装し、0または1つの要素を返す
- Flux:Publisherを実装し、N個の要素を返す。
Spring WebFlux Hello Worldの例
Let’s built a simple Spring WebFlux Hello World application. We will create a simple rest web service and use Spring Boot to run it on default Netty server. Our final project structure looks like below image. Let’s look into each component of the application one by one.
Spring WebFlux Maven Dependencies
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.spring</groupId>
<artifactId>SpringWebflux</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring WebFlux</name>
<description>Spring WebFlux Example</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jdk.version>1.9</jdk.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
アプリケーションの最も重要な依存関係はspring-boot-starter-webflux
とspring-boot-starter-parent
です。その他の依存関係はJUnitテストケースを作成するためです。
Spring WebFlux Handler
Spring WebFluxハンドラーメソッドはリクエストを処理し、Mono
またはFlux
をレスポンスとして返します。
package com.journaldev.spring.component;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class HelloWorldHandler {
public Mono<ServerResponse> helloWorld(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello World!"));
}
}
リアクティブコンポーネントMono
がServerResponse
ボディを保持していることに注意してください。また、戻り値のコンテンツタイプ、レスポンスコード、およびボディを設定するための関数チェーンを見てください。
Spring WebFlux Router
ルーターメソッドは、アプリケーションのためのルートを定義するために使用されます。これらのメソッドはRouterFunction
オブジェクトを返し、ServerResponse
のボディも保持します。
package com.journaldev.spring.component;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
public class HelloWorldRouter {
@Bean
public RouterFunction<ServerResponse> routeHelloWorld(HelloWorldHandler helloWorldHandler) {
return RouterFunctions.route(RequestPredicates.GET("/helloWorld")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), helloWorldHandler::helloWorld);
}
}
したがって、/helloWorld
に対してGETメソッドを公開し、クライアント呼び出しはプレーンテキストのレスポンスを受け入れる必要があります。
Spring Bootアプリケーション
さあ、Spring BootでシンプルなWebFluxアプリケーションを設定しましょう。
package com.journaldev.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
上記のコードを見ると、Spring WebFluxに関連するものは何もありません。しかし、spring-boot-starter-webflux
モジュールの依存関係を追加したため、Spring BootがアプリケーションをSpring WebFluxとして設定します。
Java 9モジュールサポート
アプリケーションはJava 8で実行する準備ができていますが、Java 9を使用している場合は、module-info.java
クラスも追加する必要があります。
module com.journaldev.spring {
requires reactor.core;
requires spring.web;
requires spring.beans;
requires spring.context;
requires spring.webflux;
requires spring.boot;
requires spring.boot.autoconfigure;
exports com.journaldev.spring;
}
Spring WebFlux Spring Bootアプリの実行
EclipseでSpringのサポートがある場合、上記のクラスをSpring Bootアプリとして実行することができます。コマンドラインを使用する場合は、ターミナルを開き、プロジェクトのソースディレクトリから
mvn spring-boot:run
コマンドを実行します。アプリが実行されていることを確認するために、以下のログメッセージに注目してください。また、より多くのルートと機能を追加してこのシンプルなアプリを拡張する際にも役立ちます。
2018-05-07 15:01:47.893 INFO 25158 --- [ main] o.s.w.r.f.s.s.RouterFunctionMapping : Mapped ((GET && /helloWorld) && Accept: [text/plain]) -> com.journaldev.spring.component.HelloWorldRouter$$Lambda$501/704766954@6eeb5d56
2018-05-07 15:01:48.495 INFO 25158 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-05-07 15:01:48.495 INFO 25158 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2018-05-07 15:01:48.501 INFO 25158 --- [ main] com.journaldev.spring.Application : Started Application in 1.86 seconds (JVM running for 5.542)
ログから明らかなように、アプリはポート8080でNettyサーバー上で実行されています。さあ、アプリケーションをテストしてみましょう。
Spring WebFlux App テスト
さまざまな方法でアプリをテストできます。
-
CURLコマンドを使用する
$ curl https://localhost:8080/helloWorld Hello World! $
-
ブラウザでURLを開く
-
Spring 5からのWebTestClientの使用方法 以下は、Spring 5のリアクティブウェブで
WebTestClient
を使用して当社のRESTウェブサービスをテストするためのJUnitテストプログラムです。package com.journaldev.spring; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SpringWebFluxTest { @Autowired private WebTestClient webTestClient; @Test public void testHelloWorld() { webTestClient .get().uri("/helloWorld") // GETメソッドとURI .accept(MediaType.TEXT_PLAIN) // ACCEPT-Contentを設定 .exchange() // レスポンスにアクセスする .expectStatus().isOk() // レスポンスがOKかどうかをチェック .expectBody(String.class).isEqualTo("Hello World!"); // レスポンスの型とメッセージをチェック } }
JUnitテストケースとして実行し、問題なくパスするはずです。
-
Spring Web ReactiveのWebClientを使用すると、REST Webサービスを呼び出すこともできます。
package com.journaldev.spring.client; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; public class HelloWorldWebClient { public static void main(String args[]) { WebClient client = WebClient.create("https://localhost:8080"); Mono
result = client.get() .uri("/helloWorld") .accept(MediaType.TEXT_PLAIN) .exchange(); System.out.println("Result = " + result.flatMap(res -> res.bodyToMono(String.class)).block()); } } 単純なJavaアプリケーションとして実行するだけで、適切な出力とたくさんのデバッグメッセージが表示されます。
概要
この記事では、Spring WebFluxの概要と、ハローワールドリアクティブなRestfulウェブサービスの構築方法について学びました。Springなどの人気フレームワークがリアクティブプログラミングモデルをサポートしていることは良いことです。しかし、すべての依存関係がリアクティブで非ブロッキングでない限り、アプリケーションも真にリアクティブではありません。例えば、リレーショナルデータベースのベンダーはリアクティブなドライバを持っていません。なぜなら、JDBCに依存しており、それは非リアクティブです。したがって、Hibernate APIも非リアクティブです。したがって、リレーショナルデータベースを使用している場合、まだ真にリアクティブなアプリケーションを構築することはできません。しかし、早くもそれが変わることを期待しています。
プロジェクトのコードは、私のGitHubリポジトリからダウンロードできます。
参考: 公式ドキュメント
Source:
https://www.digitalocean.com/community/tutorials/spring-webflux-reactive-programming