Spring Boot 1.4からRestTemplate
のBuilderクラス(org.springframework.boot.web.client.RestTemplateBuilder
)が追加され、自動コンフィギュレーションされます。RestTemplateBuilder
を利用すると、共通的なコンフィギュレーションを適用しつつ、個別のカスタマイズも簡単に適用することができるようになります。
Note: RestTemplateって何もの?
RestTemplate
は、Spring Frameworkが提供しているHTTPクライアントクラスです。デフォルトではJava標準のHttpURLConnection
が使われますが、Apache HttpClient、Netty 4、OkHTTP 2/3を利用してHTTP通信することもできます。
RestTemplate
については、TERASOLUNAのガイドラインをみるとよいでしょう
動作検証環境
- Spring Boot 1.4.0.RELEASE
開発プロジェクトの作成
開発プロジェクトは、SPRING INITIALIZRから作成できます。その際、Dependenciesに「Web」を指定することを忘れないでください!!
なお、本投稿の内容はArtifactに「demo-resttemplate」を指定した前提で説明しています。
RestTemplateBuilder
を使ってみる
ここでは、GitHub上で管理しているREADME.md
の中身を返却するREST APIを作ってみます。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestOperations;
@SpringBootApplication
public class DemoResttemplateApplication {
public static void main(String[] args) {
SpringApplication.run(DemoResttemplateApplication.class, args);
}
@RestController
static class DemoRestController {
private final RestOperations restOperations;
public DemoRestController(RestTemplateBuilder restTemplateBuilder) { // RestTemplateBuilderをコンストラクタインジェクション!!
this.restOperations = restTemplateBuilder.build(); // Builderのbuildメソッドを呼び出しRestTemplateを生成
}
@GetMapping("/readme")
String readme() {
// GitHubのとあるリポジトリのREADME.mdにアクセスして取得したBODYを返却
return restOperations.getForObject("https://raw.githubusercontent.com/kazuki43zoo/qiita-materials/master/README.md", String.class);
}
}
}
Spring Bootを起動します。
$ ./mvnw spring-boot:run
...
2016-08-06 11:14:35.033 INFO 46375 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-08-06 11:14:35.038 INFO 46375 --- [ main] com.example.DemoResttemplateApplication : Started DemoResttemplateApplication in 1.829 seconds (JVM running for 4.118)
Spring Bootに組み込まれているTomcatが8080ポートで立ち上がったので、http://localhost:8080/readme
にアクセスしてみましょう。ブラウザでもよいですが、ここではcurlコマンドを使ってアクセスしてみます。
$ curl -D - http://localhost:8080/readme
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 47
Date: Sat, 06 Aug 2016 02:17:13 GMT
# qiita-materials
Materials for Qiita articles
答え合わせのために本体にもアクセスしてみます。レスポンスヘッダー以外は同じですね
$ curl -D - https://raw.githubusercontent.com/kazuki43zoo/qiita-materials/master/README.md
HTTP/1.1 200 OK
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-XSS-Protection: 1; mode=block
ETag: "d84556f2372fa145a69cc7e17af7f257b4e697a9"
Content-Type: text/plain; charset=utf-8
Cache-Control: max-age=300
X-Geo-Block-List:
X-GitHub-Request-Id: 2BF94822:2E50A:1DC63E7:57A548AA
Content-Length: 47
Accept-Ranges: bytes
Date: Sat, 06 Aug 2016 02:18:10 GMT
Via: 1.1 varnish
Connection: keep-alive
X-Served-By: cache-nrt6133-NRT
X-Cache: HIT
X-Cache-Hits: 1
Vary: Authorization,Accept-Encoding
Access-Control-Allow-Origin: *
X-Fastly-Request-ID: 3d88bff242a7a53376924b0fc48778eff613931d
Expires: Sat, 06 Aug 2016 02:23:10 GMT
Source-Age: 56
# qiita-materials
Materials for Qiita articles
なお、このサンプルコードはGitHubで公開しています。
直接RestTemplate
使うのとなんか違うの?
上のようなシンプルな例だと、ほぼ同じですね
でも全く同じではないのです。それは、仮にApache HttpClient、Netty、OkHTTPのライブラリがクラスパス上にあると、それらを自動検出してくれるからです。これは直接RestTemplate
を使うとサポートされていない仕組みです。
以降では、RestTemplateBuilder
の機能を簡単に紹介していきます。
3rdパーティ製ライブラリとの連携
RestTemplate
は、Java標準のHttpURLConnection
に加えて、Apache HttpClient、Netty 4、OkHTTP 2/3を利用してHTTP通信を行うことができます。RestTemplateBuilder
を使用すると、クラスパス上にあるクラスを検査して利用するライブラリを自動検出してくれます。
仮にすべてのライブラリがクラスパス上にあった場合の優先順位は以下の通りです。
- Apache HttpClient
- OkHTTP 3
- OkHTTP 2
- Netty
- Java標準の
HttpURLConnection
(デフォルト)
タイムアウトの指定
HTTP通信のタイムアウト(接続タイムアウト、読み込みタイムアウト)を簡単に指定することができます。
this.restOperations = restTemplateBuilder
.setConnectTimeout(3000)
.setReadTimeout(5000)
.build();
Basic認証用のAuthorizationヘッダの追加
Basic認証が必要なサイトにアクセスする際に指定するユーザ名とパスワードを簡単に指定することができます。
this.restOperations = restTemplateBuilder
.basicAuthorization("username", "password")
.build();
ルートURI(ベースURI)の指定
URI内のルート(ベース)部分を指定することができます。
this.restOperations = restTemplateBuilder
.rootUri("https://raw.githubusercontent.com")
.build();
実際にリソースにアクセスする際は、ルートURI(ベースURI)以降のパスを指定するだけでOKです。
@GetMapping("/readme")
String readme() {
return restOperations.getForObject("/kazuki43zoo/qiita-materials/master/README.md", String.class);
}
HttpMessageConverter
のカスタマイズ
デフォルトでいくつかの標準的なHttpMessageConverter
と開発者がBean定義したHttpMessageConverter
が自動で組み込まれる仕組みになっていますが、明示的に追加したり、使用するクラスを限定したりすることもできます。
追加する場合は、additionalMessageConverters
メソッドを利用します。
this.restOperations = restTemplateBuilder
.additionalMessageConverters(new CustomHttpMessageConverter())
.build();
適用するHttpMessageConverter
を限定する場合は、messageConverters
メソッドを利用します。このメソッドを使うとデフォルトで組み込まれるHttpMessageConverter
は無効化されます。
this.restOperations = restTemplateBuilder
.messageConverters(new CustomHttpMessageConverter())
.build();
RestTemplate
に適用されるデフォルトのHttpMessageConverter
だけを使いたい場合は、defaultMessageConverters
メソッドを利用します。
this.restOperations = restTemplateBuilder
.defaultMessageConverters()
.build();
エラー応答のカスタマイズ
RestTemplate
では、HTTPステータスがエラー系(4xx, 5xx)の場合は、専用の例外が発生します。この動作をカスタマイズしたい場合は、ResponseErrorHandler
インタフェースの実装クラスを作成してRestTemplate
に適用する必要があります。
this.restOperations = restTemplateBuilder
.errorHandler(new CustomResponseErrorHandler())
.build();
URIテンプレートの扱いのカスタマイズ
接続先を指定する際にURIテンプレートを使用することができますが、このURIテンプレートの扱いをカスタマイズしたい場合は、UriTemplateHandler
インタフェースの実装クラスを作成してRestTemplate
に適用する必要があります。
this.restOperations = restTemplateBuilder
.uriTemplateHandler(new CustomUriTemplateHandler)
.build();
実際には、UriTemplateHandler
の実装クラスを作成するのではなく、DefaultUriTemplateHandler
をカスタマイズしたものを設定するケースの方が多いと思います。以下は、「RFC 3986のセクション2」で定義されている「unreserved」以外の文字もURLエンコーディングするようにカスタマイズしています。詳しくは「こちら」をご覧ください。
DefaultUriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler();
uriTemplateHandler.setStrictEncoding(true);
this.restOperations = restTemplateBuilder
.uriTemplateHandler(uriTemplateHandler)
.build();
HTTP通信クラスの指定
RestTemplate
は、Java標準のHttpURLConnection
に加えて、Apache HttpClient、Netty 4、OkHTTP 2/3を利用してHTTP通信を行うことができます。RestTemplateBuilder
を使用すると、クラスパス上にあるクラスを検査して利用するライブラリを自動検出してくれますが、明示的に使うクラスを指定することもできます。
this.restOperations = restTemplateBuilder
.uriTemplateHandler(new OkHttp3ClientHttpRequestFactory()) // OkHTTP 3を利用する場合
.build();
なお、自動検出を無効化することもできます。この場合は、Java標準のHttpURLConnection
が使われます。
this.restOperations = restTemplateBuilder
.detectRequestFactory(false)
.build();
RestTemplate
カスタマイズ用のコールバックインタフェースの利用
Spring Boot 1.4から、RestTemplateBuilder
経由で生成するRestTemplate
をカスタマイズするためのコールバックインタフェース(org.springframework.boot.web.client.RestTemplateCustomizer
)が追加されています。
package org.springframework.boot.web.client;
import org.springframework.web.client.RestTemplate;
public interface RestTemplateCustomizer {
void customize(RestTemplate restTemplate);
}
共通的なカスタマイズを行う場合は、個別にカスタマイズするのではなく、RestTemplateCustomizer
の実装クラスを作成するのがよいでしょう。
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {
@Override
public void customize(RestTemplate restTemplate) {
DefaultUriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler();
uriTemplateHandler.setStrictEncoding(true);
new RestTemplateBuilder()
.detectRequestFactory(false)
.basicAuthorization("uesr", "password")
.uriTemplateHandler(uriTemplateHandler)
.errorHandler(new CustomResponseErrorHandler())
.configure(restTemplate);
}
}
this.restOperations = restTemplateBuilder
.additionalCustomizers(new CustomRestTemplateCustomizer())
.build();
上の例では明示的にCustomRestTemplateCustomizer
を適用してますが、DIコンテナに登録しておけば自動でCustomRestTemplateCustomizer
を検出してくれる仕組みになっているので、以下のようにすることもできます。
@Component // DIコンテナに登録
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {
@Override
public void customize(RestTemplate restTemplate) {
...
}
}
this.restOperations = restTemplateBuilder.build(); // 明示的にadditionalCustomizersを呼び出す必要なし
テスト用のRestTemplate
(付録)
Spring Bootでは、REST APIなどのE2Eテスト向けにTestRestTemplate
というクラスを提供しています。Spring Boot 1.4より前のバージョンではRestTemplate
の子クラスとして実装されていましたが、Spring 1.4からはRestTemplate
は継承していません。正確にいうと、RestTemplate
を継承しているクラスは非推奨になり、別パッケージに同名で新しいクラスが作られています。
- 1.4から非推奨:
org.springframework.boot.test.TestRestTemplate
- 1.4から追加:
org.springframework.boot.test.web.client.TestRestTemplate
本投稿の冒頭で紹介したサンプルコードに対するE2Eテストを実装すると以下のようなコードになります。
package com.example;
import org.assertj.core.api.Assertions;
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.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // ローカルの空きポートを使って組み込みTomcatを起動
public class DemoResttemplateApplicationTests {
@Autowired
TestRestTemplate testRestTemplate; // ローカルの空きポートで起動した組み込みTomcatにアクセスするためのURIが「ルート(ベース)URI」に設定されるTestRestTemplateがDIされる
@Test
public void contextLoads() {
String resource = testRestTemplate.getForObject("/readme", String.class); // 「http://localhost:port」の部分の指定は不要
Assertions.assertThat(resource)
.isEqualTo("# qiita-materials\nMaterials for Qiita articles\n");
}
}
いい感じです。
まとめ
Spring Boot(1.4+)でRestTemplate
を使う場合は、RestTemplateBuilder
とCustomRestTemplateCustomizer
を利用しましょう。また、REST APIのE2EテストはTestRestTemplate
を使って自動化しておくことを強くオススメします。(TestRestTemplate
じゃなくてもいいけど、自動化しておきましょう!!)
参考サイト
- http://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/htmlsingle/#boot-features-restclient
- http://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/htmlsingle/#boot-features-rest-templates-test-utility
- http://terasolunaorg.github.io/guideline/5.2.0.RELEASE/ja/ArchitectureInDetail/RestClient.html