概要
先日の SpringOne Platform 2016 報告会で、Spring 5.0 では Reactor 対応の HTTP Client がリリースされると聞きました。いつ出るのかと待ちわびていたのですが、すでに Milestone バージョンは一般のユーザが Maven や Gradle を通して利用できるとわかりましたので、早速試してみました。
Reactor 対応の HTTP Client
Spring Framework の spring-web に追加された org.springframework.web.client.reactive.WebClient のことです。HTTP 通信の結果を Reactor の Mono(高々1個の結果を返す Publisher)で受け取ることができます。単に HTTP Client としても簡潔にHTTP通信の処理を記述できそうです。
注意
Reactor についてはここでは説明しません。そちらの説明は下記の記事をご参照ください。
実行環境
Java | SE 1.8.0_102 |
---|---|
OS | Windows 10 |
Eclipse | Mars 4.5.2 |
spring-web | 5.0.0.M1 |
build.gradleの修正
springframework の Maven リポジトリを追加
現在は Maven Central ではなく springframework の Maven リポジトリ http://maven.springframework.org/milestone
からダウンロードできるようです。repositories に下記を追加します。
maven {
url "http://maven.springframework.org/milestone"
}
依存の追加
dependencies に下記の依存を追加します。spring-web を動かすには reactor-netty が必要なので、それも併せて記述します。なお、同じく必要になる Reactor Core は reactor-netty の依存に含まれているので、ここで明示的に書く必要はありません。
compile 'org.springframework:spring-web:5.0.0.M1'
compile 'io.projectreactor.ipc:reactor-netty:0.5.1.RELEASE'
build.gradle 全体
apply plugin: 'java'
repositories {
mavenCentral()
maven {
url "http://maven.springframework.org/milestone"
}
}
dependencies {
compile 'org.springframework:spring-web:5.0.0.M1'
compile 'io.projectreactor.ipc:reactor-netty:0.5.1.RELEASE'
}
dependencies tree
$ gradle dependencies
:dependencies
------------------------------------------------------------
Root project
------------------------------------------------------------
compile - Dependencies for source set 'main'.
+--- org.springframework:spring-web:5.0.0.M1
| +--- org.springframework:spring-aop:5.0.0.M1
| | +--- org.springframework:spring-beans:5.0.0.M1
| | | \--- org.springframework:spring-core:5.0.0.M1
| | | \--- commons-logging:commons-logging:1.2
| | \--- org.springframework:spring-core:5.0.0.M1 (*)
| +--- org.springframework:spring-beans:5.0.0.M1 (*)
| +--- org.springframework:spring-context:5.0.0.M1
| | +--- org.springframework:spring-aop:5.0.0.M1 (*)
| | +--- org.springframework:spring-beans:5.0.0.M1 (*)
| | +--- org.springframework:spring-core:5.0.0.M1 (*)
| | \--- org.springframework:spring-expression:5.0.0.M1
| | \--- org.springframework:spring-core:5.0.0.M1 (*)
| \--- org.springframework:spring-core:5.0.0.M1 (*)
\--- io.projectreactor.ipc:reactor-netty:0.5.1.RELEASE
+--- io.netty:netty-all:4.1.3.Final
+--- io.projectreactor.ipc:reactor-ipc:0.5.1.RELEASE
| \--- io.projectreactor:reactor-core:3.0.1.RELEASE
| \--- org.reactivestreams:reactive-streams:1.0.0
\--- io.projectreactor:reactor-core:3.0.1.RELEASE (*)
(*) - dependencies omitted (listed previously)
Reactive WebClient を使う
ほぼ org.springframework.web.client.reactive.WebClient の Javadoc に書いてあるサンプルコードのままで試せます。ただ、サンプルコードを動かすのにいくつか注意点があったので、下記で述べます。
static import の追加
サンプルコード中で org.springframework.web.client.reactive.ClientWebRequestBuilders.get
と org.springframework.web.client.reactive.ResponseExtractors.body
を static import しているので追加します。
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get;
import static org.springframework.web.client.reactive.ResponseExtractors.body;
WebClientオブジェクトを作ってYahoo! JAPANのトップページをGETでプレインテキスト形式で取得するリクエストを実行し、取得したbodyをStringで取り出して標準出力に表示するだけの簡単なステップを書く
メソッドチェインできるように設計されているので、動かすだけなら1ステップで書けます。
Reactive の WebClient を生成
new WebClient(new ReactorClientHttpConnector())
Yahoo! JAPAN のトップページをGETリクエストでプレインテキスト形式で取得
.perform(get("http://www.yahoo.co.jp").accept(MediaType.TEXT_PLAIN))
レスポンスの body を String 型で取り出す
.extract(body(String.class))
標準出力で表示
.subscribe(System.out::println);
全体
new WebClient(new ReactorClientHttpConnector()) // Reactive の WebClient を生成
.perform(get("http://www.yahoo.co.jp").accept(MediaType.TEXT_PLAIN)) //
.extract(body(String.class))
.subscribe(System.out::println);
非同期処理の終了まで待機
Reactive WebClient の処理は非同期処理ですので、今回のようなごく小さいサンプルですと、メインスレッドを待機させておかないと結果を取得・表示し終える前にプログラムが終了します。
それを防ぐために CountDownLatch を使います。
CountDownLatch は初期化時に数値を指定し、その回数分 countDown() メソッドが呼ばれるまでスレッドを止めておけるクラスです。なお、このクラスは Java の標準ライブラリ(1.5~)に含まれています。
CountDownLatch の使用例
final CountDownLatch latch = new CountDownLatch(1);
latch.countDown();
latch.await();
今回のケースでは subscribe で標準出力での表示を終えてから countDown() を実行するようにコードを修正します。
ソースコード全体
コメントやパッケージ宣言は省きます。
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get;
import static org.springframework.web.client.reactive.ResponseExtractors.body;
import java.util.concurrent.CountDownLatch;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.client.reactive.WebClient;
public class Main {
public static void main(String[] args) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
new WebClient(new ReactorClientHttpConnector())
.perform(get("http://www.yahoo.co.jp").accept(MediaType.TEXT_PLAIN))
.extract(body(String.class))
.subscribe(body -> {
System.out.println(body);
latch.countDown();
});
latch.await();
}
}
ほか、注意点
Reactor の注意点ですが、 extract で取得できる Mono オブジェクトを subscribe するまでは一切の処理が実行されません。
まとめ
spring-web の org.springframework.web.client.reactive.WebClient により、お手軽に非同期での HTTP 通信処理が実装できました。今の段階で Milestone のライブラリを一般のユーザが使っていいのか、本番適用が不可なのかは私の調べが足りなくてわかっていませんが、個人で遊ぶレベルなら十分面白いライブラリだと思います。正式なリリースが楽しみです。
参考
ソースコード
GitHub に上げてあります。
追記
@making@github さんがコメントで下記のご指摘をくださいました。
mainで叩くだけならCountDownlatch使わず、Mono#block()を使うって手も。
自分でも確認してみたところ、その通りでした。今回のようなmainスレッドの終了を抑止したいだけのケースでは Mono#block()
を使った方がコードがシンプルになるので、良いと思います。
String body = new WebClient(new ReactorClientHttpConnector())
.perform(get("http://www.yahoo.co.jp").accept(MediaType.TEXT_PLAIN))
.extract(body(String.class))
.block();
System.out.println(body);