Spring 5.0 の Reactive WebClient を使う

# 概要
先日の 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://docs.spring.io/spring-framework/docs/5.0.0.BUILD-SNAPSHOT/javadoc-api/org/springframework/web/client/reactive/WebClient.html) のことです。HTTP 通信の結果を Reactor の Mono(高々1個の結果を返す Publisher)で受け取ることができます。単に HTTP Client としても簡潔にHTTP通信の処理を記述できそうです。

## 注意
Reactor についてはここでは説明しません。そちらの説明は下記の記事をご参照ください。

1. [JavaでReactiveプログラミング ~Reactor~](http://qiita.com/miya-masa/items/ee008d30ef88bc9d4faa)
2. [Reactor Core 2.5: もう一つのJava向けReactive Extensions実装](http://qiita.com/sugibuchi/items/e933ae4b5ec512054de2)

## 実行環境

| 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 に下記を追加します。

```groovy:mavenリポジトリの追加
    maven {
      url "http://maven.springframework.org/milestone"
    }
```

## 依存の追加
dependencies に下記の依存を追加します。spring-web を動かすには reactor-netty が必要なので、それも併せて記述します。なお、同じく必要になる [Reactor Core](http://projectreactor.io/) は reactor-netty の依存に含まれているので、ここで明示的に書く必要はありません。

```groovy:依存の追加
compile 'org.springframework:spring-web:5.0.0.M1'
compile 'io.projectreactor.ipc:reactor-netty:0.5.1.RELEASE'
```

### build.gradle 全体

```groovy: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

```java:gradle&nbspdependencies
$ 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](http://docs.spring.io/spring-framework/docs/5.0.0.BUILD-SNAPSHOT/javadoc-api/org/springframework/web/client/reactive/WebClient.html) に書いてあるサンプルコードのままで試せます。ただ、サンプルコードを動かすのにいくつか注意点があったので、下記で述べます。

## static import の追加
サンプルコード中で ```org.springframework.web.client.reactive.ClientWebRequestBuilders.get``` と ```org.springframework.web.client.reactive.ResponseExtractors.body``` を static import しているので追加します。

```java
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get;
import static org.springframework.web.client.reactive.ResponseExtractors.body;
```

## WebClientオブジェクトを作ってYahoo!&nbspJAPANのトップページをGETでプレインテキスト形式で取得するリクエストを実行し、取得したbodyをStringで取り出して標準出力に表示するだけの簡単なステップを書く
メソッドチェインできるように設計されているので、動かすだけなら1ステップで書けます。

### Reactive の WebClient を生成

```java
new WebClient(new ReactorClientHttpConnector())
```

### Yahoo! JAPAN のトップページをGETリクエストでプレインテキスト形式で取得

```java
            .perform(get("http://www.yahoo.co.jp").accept(MediaType.TEXT_PLAIN))
```

### レスポンスの body を String 型で取り出す

```java
            .extract(body(String.class))
```

### 標準出力で表示

```java
            .subscribe(System.out::println);
```

### 全体

```java:WebClientオブジェクトを作ってYahoo!&nbspJAPANのトップページをGETでプレインテキスト形式で取得するリクエストを実行し、取得したbodyをStringで取り出して標準出力に表示するだけの簡単なステップ
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](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CountDownLatch.html) を使います。
CountDownLatch は初期化時に数値を指定し、その回数分 countDown() メソッドが呼ばれるまでスレッドを止めておけるクラスです。なお、このクラスは Java の標準ライブラリ(1.5~)に含まれています。

### CountDownLatch の使用例

```java:CountDownLatchオブジェクトをカウント1で初期化
final CountDownLatch latch = new CountDownLatch(1);
```

```java:カウントを1減らす
latch.countDown();
```

```java:カウントが0になるまで待機
latch.await();
```

今回のケースでは subscribe で標準出力での表示を終えてから countDown() を実行するようにコードを修正します。

## ソースコード全体
コメントやパッケージ宣言は省きます。

```java:ソースコード全体
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://docs.spring.io/spring-framework/docs/5.0.0.BUILD-SNAPSHOT/javadoc-api/org/springframework/web/client/reactive/WebClient.html) により、お手軽に非同期での HTTP 通信処理が実装できました。今の段階で Milestone のライブラリを一般のユーザが使っていいのか、本番適用が不可なのかは私の調べが足りなくてわかっていませんが、個人で遊ぶレベルなら十分面白いライブラリだと思います。正式なリリースが楽しみです。

----
# 参考
1. [Reactive Programming with Spring 5.0 M1](https://spring.io/blog/2016/07/28/reactive-programming-with-spring-5-0-m1)
2. [org.springframework.web.client.reactive.WebClient](http://docs.spring.io/spring-framework/docs/5.0.0.BUILD-SNAPSHOT/javadoc-api/org/springframework/web/client/reactive/WebClient.html)

## ソースコード
GitHub に上げてあります。

- [spring_web_client_example(GitHub)](https://github.com/toastkidjp/spring_web_client_example/tree/qiita)

----
# 追記
@making@github さんがコメントで下記のご指摘をくださいました。

> mainで叩くだけならCountDownlatch使わず、Mono#block()を使うって手も。

自分でも確認してみたところ、その通りでした。今回のようなmainスレッドの終了を抑止したいだけのケースでは ```Mono#block()``` を使った方がコードがシンプルになるので、良いと思います。

```java:CountDownLatchではなく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);
```