5.2. RESTクライアント(HTTPクライアント)

目次

5.2.1. Overview

本節では、Spring Frameworkが提供するorg.springframework.web.client.RestTemplateを使用してRESTful Web Service(REST API)を呼び出す実装方法について説明する。

5.2.1.1. RestTemplate とは

RestTemplateは、REST API(Web API)を呼び出すためのメソッドを提供するクラスであり、 Spring Frameworkが提供するHTTPクライアントである。

具体的な実装方法の説明を行う前に、RestTemplateがどのようにREST API(Web API)にアクセスしているかを説明する。

Constitution of RestTemplate
項番 コンポーネント 説明
(1)
アプリケーション
RestTemplateのメソッドを呼び出して、REST API(Web API)の呼び出し依頼を行う。
(2)
RestTemplate
HttpMessageConverterを使用して、Javaオブジェクトをリクエストボディに設定する電文(JSON等)に変換する。
(3)

ClientHttpRequestFactoryからClientHttpRequestを取得して、電文(JSON等)の送信依頼を行う。
(4)
ClientHttpRequest
電文(JSON等)をリクエストボディに設定して、REST API(Web API)にHTTP経由でリクエストを行う。
(5)
RestTemplate
ResponseErrorHandlerを使用して、HTTP通信のエラー判定及びエラー処理を行う。
(6)
ResponseErrorHandler
ClientHttpResponseからレスポンスデータを取得して、エラー判定及びエラー処理を行う。
(7)
RestTemplate
HttpMessageConverterを使用して、レスポンスボディに設定されている電文(JSON等)をJavaオブジェクトに変換する。
(8)

REST API(Web API)の呼び出し結果(Javaオブジェクト)をアプリケーションへ返却する。

Note

非同期処理への対応

REST APIからの応答を別スレッドで処理したい場合(非同期で処理したい場合)は、 RestTemplateの代わりにorg.springframework.web.client.AsyncRestTemplateを使用すればよい。 非同期処理の実装例については、非同期リクエスト を参照されたい。

5.2.1.1.1. HttpMessageConverter

org.springframework.http.converter.HttpMessageConverterは、アプリケーションで扱うJavaオブジェクトとサーバと通信するための電文(JSON等)を相互に変換するためのインタフェースである。

RestTemplateを使用した場合、デフォルトで以下のHttpMessageConverterの実装クラスが登録される。

デフォルトで登録されるHttpMessageConverter
項番 クラス名 説明 サポート型
(1)
org.springframework.http.converter.
ByteArrayHttpMessageConverter
「HTTPボディ(テキスト or バイナリデータ)⇔バイト配列」変換用のクラス。
デフォルトですべてのメディアタイプ(*/*)をサポートする。
byte[]
(2)
org.springframework.http.converter.
StringHttpMessageConverter
「HTTPボディ(テキスト)⇔文字列」変換用のクラス。
デフォルトですべてのテキストメディアタイプ(text/*)をサポートする。
String
(3)
org.springframework.http.converter.
ResourceHttpMessageConverter
「HTTPボディ(バイナリデータ)⇔Springのリソースオブジェクト」変換用のクラス。
デフォルトですべてのメディアタイプ(*/*)をサポートする。
Resource [1]
(4)
org.springframework.http.converter.xml.
SourceHttpMessageConverter
「HTTPボディ(XML)⇔XMLソースオブジェクト」変換用のクラス。
デフォルトでXML用のメディアタイプ(text/xml,application/xml,application/*-xml)をサポートする。
Source [2]
(5)
org.springframework.http.converter.support.
AllEncompassingFormHttpMessageConverter
「HTTPボディ⇔MultiValueMapオブジェクト」変換用のクラス。
FormHttpMessageConverterの拡張クラスで、multipartのパートデータとしてXMLとJSONへの変換がサポートされている。
デフォルトでフォームデータ用のメディアタイプ(application/x-www-form-urlencoded,multipart/form-data)をサポートする。
  • メディアタイプがapplication/x-www-form-urlencodedの場合、MultiValueMap<String, String>として読込/書込される。
  • メディアタイプがmultipart/form-dataの場合、MultiValueMap<String, Object>として書込され、ObjectAllEncompassingFormHttpMessageConverter内に別途設定されるHttpMessageConveterで変換される。 (注意: Note 参照)
デフォルトで登録されるパートデータ変換用のHttpMessageConveterは、AllEncompassingFormHttpMessageConverterFormHttpMessageConverterのソースを参照されたい。なお、任意のHttpMessageConverterを登録することもできる。
MultiValueMap [3]

Note

AllEncompassingFormHttpMessageConverterのメディアタイプがmultipart/form-dataの場合について

メディアタイプがmultipart/form-dataの場合、「MultiValueMapオブジェクト から HTTPボディ」への変換は可能だが、 「HTTPボディ から MultiValueMapオブジェクト」への変換は現状サポートされていない。 よって、「HTTPボディ から MultiValueMapオブジェクト」への変換を行いたい場合は、独自に実装する必要がある。

依存ライブラリがクラスパス上に存在する場合に登録されるHttpMessageConverter
項番 クラス名 説明 サポート型
(6)
org.springframework.http.converter.feed.
AtomFeedHttpMessageConverter
「HTTPボディ(Atom)⇔Atomフィードオブジェクト」変換用のクラス。
デフォルトでATOM用のメディアタイプ(application/atom+xml)をサポートする。
(ROMEがクラスパスに存在する場合に登録される)
Feed [4]
(7)
org.springframework.http.converter.feed.
RssChannelHttpMessageConverter
「HTTPボディ(RSS)⇔Rssチャネルオブジェクト」変換用のクラス。
デフォルトでRSS用のメディアタイプ(application/rss+xml)をサポートする。
(ROMEがクラスパスに存在する場合に登録される)
Channel [5]
(8)
org.springframework.http.converter.json.
MappingJackson2HttpMessageConverter
「HTTPボディ(JSON)⇔JavaBean」変換用のクラス。
デフォルトでJSON用のメディアタイプ(application/json,application/*+json)をサポートする。
(Jackson2がクラスパスに存在する場合に登録される)
Object (JavaBean)
Map
(9)
org.springframework.http.converter.xml.
MappingJackson2XmlHttpMessageConverter
「HTTPボディ(XML)⇔JavaBean」変換用のクラス。
デフォルトでXML用のメディアタイプ(text/xml,application/xml,application/*-xml)をサポートする。
(Jackson-dataformat-xmlがクラスパスに存在する場合に登録される)
Object (JavaBean)
Map
(10)
org.springframework.http.converter.xml.
Jaxb2RootElementHttpMessageConverter
「HTTPボディ(XML)⇔JavaBean」変換用のクラス。
デフォルトでXML用のメディアタイプ(text/xml,application/xml,application/*-xml)をサポートする。
(JAXBがクラスパスに存在する場合に登録される)

Note

Java SE 11環境にてJAXBをクラスパスに登録するにはJAXBの削除を参照されたい。

Object (JavaBean)
(11)
org.springframework.http.converter.json.
GsonHttpMessageConverter
「HTTPボディ(JSON)⇔JavaBean」変換用のクラス。
デフォルトでJSON用のメディアタイプ(application/json,application/*+json)をサポートする。
(Gsonがクラスパスに存在する場合に登録される)
Object (JavaBean)
Map

[1]org.springframework.core.ioパッケージのクラス
[2]javax.xml.transformパッケージのクラス
[3]org.springframework.utilパッケージのクラス
[4]com.rometools.rome.feed.atomパッケージのクラス
[5]com.rometools.rome.feed.rssパッケージのクラス

5.2.1.1.2. ClientHttpRequestFactory

RestTemplateは、サーバとの通信処理を以下の3つのインタフェースの実装クラスに委譲することで実現している。

  • org.springframework.http.client.ClientHttpRequestFactory
  • org.springframework.http.client.ClientHttpRequest
  • org.springframework.http.client.ClientHttpResponse

この3つのインタフェースのうち、開発者が意識するのはClientHttpRequestFactoryである。 ClientHttpRequestFactoryは、サーバとの通信処理を行うクラス(ClientHttpRequestClientHttpResponseインタフェースの実装クラス)を解決する役割を担っている。

なお、Spring Frameworkが提供している主なClientHttpRequestFactoryの実装クラスは以下の通りである。

Spring Frameworkが提供している主なClientHttpRequestFactoryの実装クラス
項番 クラス名 説明
(1)
org.springframework.http.client.
SimpleClientHttpRequestFactory
Java SE標準の HttpURLConnectionのAPIを使用して通信処理(同期、非同期)を行うための実装クラス。(デフォルトで使用される実装クラス)
(2)
org.springframework.http.client.
Netty4ClientHttpRequestFactory
Netty 4のAPIを使用して通信処理(同期、非同期)を行うための実装クラス。
(3)
org.springframework.http.client.
HttpComponentsClientHttpRequestFactory
Apache HttpComponents HttpClientのAPIを使用して同期型の通信処理を行うための実装クラス。(HttpClient 4.3以上が必要)
(4)
org.springframework.http.client.
HttpComponentsAsyncClientHttpRequestFactory
Apache HttpComponents HttpAsyncClientのAPIを使用して非同期型の通信処理を行うための実装クラス。(HttpAsyncClient 4.0以上が必要)
(5)
org.springframework.http.client.
OkHttpClientHttpRequestFactory
Square OkHttpのAPIを使用して通信処理(同期、非同期)を行うための実装クラス。

Note

使用するClientHttpRequestFactoryの実装クラスについて

RestTemplateが使用するデフォルト実装はSimpleClientHttpRequestFactoryであり、本ガイドラインでもSimpleClientHttpRequestFactoryを使用した際の実装例となっている。 Java SEのHttpURLConnectionで要件が満たせない場合は、Netty、Apache Http Componentsなどのライブラリの利用を検討されたい。

5.2.1.1.3. ResponseErrorHandler

RestTemplateは、サーバとの通信エラーのハンドリングをorg.springframework.web.client.ResponseErrorHandlerインタフェースに委譲することで実現している。

ResponseErrorHandlerには、

  • エラー判定を行うメソッド(hasError)
  • エラー処理を行うメソッド(handleError)

が定義されており、Spring Frameworkはデフォルト実装としてorg.springframework.web.client.DefaultResponseErrorHandlerを提供している。

DefaultResponseErrorHandlerは、サーバから応答されたHTTPステータスコードの値によって以下のようなエラー処理を行う。

  • レスポンスコードが正常系(2xx)の場合は、エラー処理は行わない。
  • レスポンスコードがクライアントエラー系(4xx)の場合は、org.springframework.web.client.HttpClientErrorExceptionを発生させる。
  • レスポンスコードがサーバエラー系(5xx)の場合は、org.springframework.web.client.HttpServerErrorExceptionを発生させる。
  • レスポンスコードが未定義のコード(ユーザ定義のカスタムコード)の場合は、org.springframework.web.client.UnknownHttpStatusCodeExceptionを発生させる。

Note

エラー時のレスポンスデータの取得方法

エラー時のレスポンスデータ(HTTPステータスコード、レスポンスヘッダ、レスポンスボディなど)は、例外クラスのgetterメソッドを呼び出すことで取得することができる。

5.2.1.1.4. ClientHttpRequestInterceptor

org.springframework.http.client.ClientHttpRequestInterceptorは、サーバとの通信の前後に共通的な処理を実装するためのインタフェースである。

ClientHttpRequestInterceptorを使用すると、

  • サーバとの通信ログ
  • 認証ヘッダの設定

といった共通的な処理をRestTemplateに適用することができる。

Note

ClientHttpRequestInterceptorの動作仕様

ClientHttpRequestInterceptorは複数適用することができ、指定した順番でチェーン実行される。 これはサーブレットフィルタの動作によく似ており、最後に実行されるチェーン先としてClientHttpRequestによるHTTP通信処理が登録されている。 例えば、ある条件に一致した際にサーバとの通信処理をキャンセルしたいという要件があった場合は、チェーン先を呼びださなければよい。

この仕組みを活用すると、

  • サーバとの通信の閉塞
  • 通信処理のリトライ

といった処理を適用することもできる。

5.2.2. How to use

本節では、RestTemplateを使用したクライアント処理の実装方法について説明する。

Note

RestTemplateがサポートするHTTPメソッドについて

本ガイドラインでは、GETメソッドとPOSTメソッドを使用したクライアント処理の実装例のみを紹介するが、 RestTemplateは他のHTTPメソッド(PUT, PATCH, DELETE, HEAD, OPTIONSなど)もサポートしており、同じような要領で使用することができる。 詳細はRestTemplateのJavadocを参照されたい。

5.2.2.1. RestTemplateのセットアップ

RestTemplateを使用する場合は、RestTemplateをDIコンテナに登録し、RestTemplateを利用するコンポーネントにインジェクションする。

5.2.2.1.1. 依存ライブラリ設定

RestTemplateを使用するためにpom.xmlに、Spring Frameworkのspring-webライブラリを追加する。
マルチプロジェクト構成の場合は、domainプロジェクトのpom.xmlに追加する。
<dependencies>

    <!-- (1) -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>

</dependencies>

Note

上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 上記の依存ライブラリはterasoluna-gfw-parentが依存しているSpring Bootで管理されている。

項番 説明
(1)
Spring Frameworkのspring-webライブラリをdependenciesに追加する。

5.2.2.1.2. RestTemplateのbean定義

RestTemplateのbean定義を行い、DIコンテナに登録する。

bean定義ファイル(applicationContext.xml)の定義例

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate" /> <!-- (1) -->
項番 説明
(1)
RestTemplateをデフォルト設定のまま利用する場合は、デフォルトコンストラクタを使用してbeanを登録する。

Note

RestTemplateのカスタマイズ方法

HTTP通信処理をカスタマイズする場合は、以下のようなbean定義となる。

<bean id="clientHttpRequestFactory"
      class="org.springframework.http.client.SimpleClientHttpRequestFactory"> <!-- (1) -->
    <!-- Set properties for customize a http communication (omit on this sample) -->
</bean>

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <constructor-arg ref="clientHttpRequestFactory" /> <!-- (2) -->
</bean>
項番 説明
(1)
ClientHttpRequestFactoryのbean定義を行う。
本ガイドラインではタイムアウトの設定をカスタマイズする方法を紹介している。詳細は 通信タイムアウトの設定 を参照されたい。
(2)
ClientHttpRequestFactoryを引数に指定するコンストラクタを使用してbeanを登録する。

なお、HttpMessageConverterResponseErrorHandlerClientHttpRequestInterceptorのカスタマイズ方法については、

を参照されたい。

5.2.2.1.3. RestTemplateの利用

RestTemplateを利用する場合は、DIコンテナに登録されているRestTemplateをインジェクションする。

RestTemplateのインジェクション例

@Service
public class AccountServiceImpl implements AccountService {

    @Inject
    RestTemplate restTemplate;

    // ...

}

5.2.2.2. GETリクエストの送信

RestTemplateは、GETリクエストを行うためのメソッドを複数提供している。

  • 通常はgetForObjectメソッド又はgetForEntityメソッドを使用する。
  • 任意のヘッダを設定するなどリクエストに細かい設定を行いたい場合は、org.springframework.http.RequestEntityexchangeメソッドを使用する。

5.2.2.2.1. getForObjectメソッドを使用した実装

レスポンスボディのみ取得できればよい場合は、getForObjectメソッドを使用する。

getForObjectメソッドの使用例

フィールド宣言部

@Value("${api.url:http://localhost:8080/api}")
URI uri;

メソッド内部

User user = restTemplate.getForObject(uri, User.class); // (1)
項番 説明
(1)
getForObjectメソッドを使用した場合は、戻り値はレスポンスボディの値になる。
レスポンスボディのデータはHttpMessageConverterによって第2引数に指定したJavaクラスへ変換された後、返却される。

5.2.2.2.2. getForEntityメソッドを使用した実装

HTTPステータスコード、レスポンスヘッダ、レスポンスボディを取得する必要がある場合は、getForEntityメソッドを使用する。

getForEntityメソッドの使用例

ResponseEntity<User> responseEntity =
        restTemplate.getForEntity(uri, User.class); // (1)
HttpStatus statusCode = responseEntity.getStatusCode(); // (2)
HttpHeaders header = responseEntity.getHeaders(); // (3)
User user = responseEntity.getBody(); // (4)
項番 説明
(1)
getForEntityメソッドを使用した場合は、戻り値はorg.springframework.http.ResponseEntityとなる。
(2)
HTTPステータスコードはgetStatusCodeメソッドを用いて取得する。
(3)
レスポンスヘッダはgetHeadersメソッドを用いて取得する。
(4)
レスポンスボディはgetBodyメソッドを用いて取得する。

Note

ResponseEntityとは

ResponseEntityはHTTPレスポンスを表すクラスで、HTTPステータスコード、レスポンスヘッダ、レスポンスボディの情報を取得することができる。 詳細はResponseEntityのJavadocを参照されたい。

5.2.2.2.3. exchangeメソッドを使用した実装

リクエストヘッダを指定する必要がある場合は、org.springframework.http.RequestEntityを生成しexchangeメソッドを使用する。

exchangeメソッドの使用例

import部

import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;

フィールド宣言部

@Value("${api.url:http://localhost:8080/api}")
URI uri;

メソッド内部

RequestEntity requestEntity = RequestEntity
        .get(uri)//(1)
        .build();//(2)

ResponseEntity<User> responseEntity =
        restTemplate.exchange(requestEntity, User.class);//(3)

User user = responseEntity.getBody();//(4)
項番 説明
(1)
RequestEntitygetメソッドを使用し、GETリクエスト用のリクエストビルダを生成する。
パラメータにURIを設定する。
(2)
RequestEntity.HeadersBuilderbuildメソッドを使用し、RequestEntityオブジェクトを作成する。
(3)
exchangeメソッドを使用し、リクエストを送信する。第二引数に、レスポンスデータの型を指定する。
レスポンスは、ResponseEntity<T>になる。型パラメータに、レスポンスデータの型を設定する。
(4)
getBodyメソッドを使用し、レスポンスボディのデータを取得する。

Note

RequestEntityとは

RequestEntityはHTTPリクエストを表すクラスで、接続URI、HTTPメソッド、リクエストヘッダ、リクエストボディを設定することができる。 詳細はRequestEntityのJavadocを参照されたい。

なお、リクエストヘッダの設定方法については、リクエストヘッダの設定 を参照されたい。

5.2.2.3. POSTリクエストの送信

RestTemplateは、POSTリクエストを行うためのメソッドを複数提供している。

  • 通常は、postForObjectpostForEntityを使用する。
  • 任意のヘッダを設定するなどリクエストに細かい設定を行いたい場合は、RequestEntityexchangeメソッドを使用する。

5.2.2.3.1. postForObjectメソッドを使用した実装

POSTした結果としてレスポンスボディのみ取得できればよい場合は、postForObjectメソッドを使用する。

postForObjectメソッドの使用例

User user = new User();

//...

User user = restTemplate.postForObject(uri, user, User.class); // (1)
項番 説明
(1)
postForObjectメソッドは、簡易にPOSTリクエストを実装できる。
第二引数には、HttpMessageConverterによってリクエストボディに変換されるJavaオブジェクトを設定する。
postForObjectメソッドを使用した場合は、戻り値はレスポンスボディの値になる。

5.2.2.3.2. postForEntityメソッドを使用した実装

POSTした結果としてHTTPステータスコード、レスポンスヘッダ、レスポンスボディを取得する必要がある場合は、postForEntityメソッドを使用する。

postForEntityメソッドの使用例

User user = new User();

//...

ResponseEntity<User> responseEntity =
        restTemplate.postForEntity(uri, user, User.class); // (1)
項番 説明
(1)
postForEntityメソッドもgetForObjectメソッドと同様に簡易にPOSTリクエストを実装できる。
postForEntityメソッドを使用した場合は、戻り値はResponseEntityとなる。
レスポンスボディの値は、ResponseEntityから取得する。

5.2.2.3.3. exchangeメソッドを使用した実装

リクエストヘッダを指定する必要がある場合は、RequestEntityを生成しexchangeメソッドを使用する。

exchangeメソッドの使用例

import部

import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;

フィールド宣言部

@Value("${api.url:http://localhost:8080/api}")
URI uri;

メソッド内部

User user = new User();

//...

RequestEntity<User> requestEntity = RequestEntity//(1)
        .post(uri)//(2)
        .body(user);//(3)

ResponseEntity<User> responseEntity =
        restTemplate.exchange(requestEntity, User.class);//(4)
項番 説明
(1)
RequestEntityを使用して、リクエストを生成する。型パラメータに、リクエストボディに設定するデータの型を指定する。
(2)
postメソッドを使用し、POSTリクエスト用のリクエストビルダを生成する。パラメータにURIを設定する。
(3)
RequestEntity.BodyBuilderbodyメソッドを使用し、RequestEntityオブジェクトを作成する。
パラメータにリクエストボディに変換するJavaオブジェクトを設定する。
(4)
exchangeメソッドを使用し、リクエストを送信する。

Note

リクエストヘッダの設定方法

リクエストヘッダの設定方法については、リクエストヘッダの設定 を参照されたい。

5.2.2.4. コレクション形式のデータ取得

サーバから応答されるレスポンスボディの電文(JSON等)がコレクション形式の場合は、以下のような実装となる。

コレクション形式のデータの取得例

ResponseEntity<List<User>> responseEntity = //(1)
    restTemplate.exchange(requestEntity, new ParameterizedTypeReference<List<User>>(){}); //(2)

List<User> userList = responseEntity.getBody();//(3)
項番 説明
(1)
ResponseEntityの型パラメータにList<レスポンスデータの型>を指定する。
(2)
exchangeメソッドの第二引数にorg.springframework.core.ParameterizedTypeReferenceのインスタンスを指定し、型パラメータにList<レスポンスデータの型>を指定する。
(2)
getBodyメソッドで、レスポンスボディのデータを取得する。

5.2.2.5. リクエストヘッダの設定

RequestEntityexchangeメソッドを使用すると、RequestEntityのメソッドを使用して特定のヘッダ及び任意のヘッダを設定することができる。 詳細はRequestEntityのJavadocを参照されたい。

本ガイドラインでは、

について説明する。

5.2.2.5.1. Content-Typeヘッダの設定

サーバへデータを送信する場合は、通常Content-Typeヘッダの指定が必要となる。

Content-Typeヘッダの設定例

User user = new User();

//...

RequestEntity<User> requestEntity = RequestEntity
        .post(uri)
        .contentType(MediaType.APPLICATION_JSON) // (1)
        .body(user);
項番 説明
(1)
RequestEntity.BodyBuildercontentTypeメソッドを使用し、Context-Typeヘッダの値を指定する。
上記の実装例では、データ形式がJSONであることを示す「application/json」を設定している。

5.2.2.5.2. Acceptヘッダの設定

サーバから取得するデータの形式を指定する場合は、Acceptヘッダの指定が必要となる。 サーバが複数のデータ形式のレスポンスをサポートしていない場合は、Acceptヘッダを明示的に指定しなくてもよいケースもある。

Acceptヘッダの設定例

User user = new User();

//...

RequestEntity<User> requestEntity = RequestEntity
        .post(uri)
        .accept(MediaType.APPLICATION_JSON) // (1)
        .body(user);
項番 説明
(1)
RequestEntity.HeadersBuilderacceptメソッドを使用して、Acceptヘッダの値を設定する。
上記の実装例では、取得可能なデータ形式がJSONであることを示す「application/json」を設定している。

5.2.2.5.3. 任意のリクエストヘッダの設定

サーバへアクセスするために、リクエストヘッダの設定が必要になるケースがある。

任意ヘッダの設定例

User user = new User();

//...

RequestEntity<User> requestEntity = RequestEntity
        .post(uri)
        .header("Authorization", "Basic " + base64Credentials) // (1)
        .body(user);
項番 説明
(1)
RequestEntity.HeadersBuilderheaderメソッドを使用してリクエストヘッダの名前と値を設定する。
上記の実装例では、AuthorizationヘッダにBasic認証に必要な資格情報を設定している。

5.2.2.6. エラーハンドリング

5.2.2.6.1. 例外ハンドリング(デフォルトの動作)

RestTemplateのデフォルト実装(DefaultResponseErrorHandler)では、

  • レスポンスコードがクライアントエラー系(4xx)の場合は、HttpClientErrorException
  • レスポンスコードがサーバエラー系(5xx)の場合は、HttpServerErrorException
  • レスポンスコードが未定義のコード(ユーザ定義のカスタムコード)の場合は、UnknownHttpStatusCodeException

が発生するため、必要に応じてこれらの例外をハンドリングする必要がある。

例外ハンドリングの実装例

Note

以下の実装例は、サーバエラーが発生した際の例外ハンドリングの一例である。

アプリケーションの要件に応じて適切な例外ハンドリングを行うこと。

フィールド宣言部

@Value("${api.retry.maxCount}")
int retryMaxCount;

@Value("${api.retry.retryWaitTimeCoefficient}")
int retryWaitTimeCoefficient;

メソッド内部

int retryCount = 0;
while (true) {
    try {

        responseEntity = restTemplate.exchange(requestEntity, String.class);

        if (logger.isInfoEnabled()) {
            logger.info("Success({}) ", responseEntity.getStatusCode());
        }

        break;

    } catch (HttpServerErrorException e) { // (1)

        if (retryCount == retryMaxCount) {
            throw e;
        }

        retryCount++;

        if (logger.isWarnEnabled()) {
            logger.warn("An error ({}) occurred on the server. (The number of retries:{} Times)", e.getStatusCode(),
                retryCount);
        }

        try {
            Thread.sleep(retryWaitTimeCoefficient * retryCount);
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
        }

        //...
    }

}
項番 説明
(1)
例外をキャッチしてエラー処理を行う。サーバエラー(500系)の場合、HttpServerErrorExceptionをキャッチする。

5.2.2.6.2. ResponseEntityの返却(エラーハンドラの拡張)

org.springframework.web.client.ResponseErrorHandlerインタフェースの実装クラスをRestTemplateに設定することで、独自のエラー処理を行うことができる。

以下の例では、サーバエラー及びクライアントエラーが発生した場合でもResponseEntityを返すようにエラーハンドラを拡張している。

エラーハンドラの実装クラスの作成例

import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;

public class CustomErrorHandler extends DefaultResponseErrorHandler { // (1)

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        //Don't throw Exception.
    }

}
項番 説明
(1)
ResponseErrorHandlerインタフェースの実装クラスを作成する。
上記の作成例では、デフォルトのエラーハンドラの実装クラスであるDefaultResponseErrorHandlerを拡張し、
サーバエラー及びクライアントエラーが発生した際に例外を発生させずにResponseEntityが返るようにしている。

bean定義ファイル(applicationContext.xml)の定義例

<bean id="customErrorHandler" class="com.example.restclient.CustomErrorHandler" /> <!-- (1) -->

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="errorHandler" ref="customErrorHandler" /><!-- (2) -->
</bean>
項番 説明
(1)
ResponseErrorHandlerの実装クラスのbean定義を行う。
(2)
errorHandlerプロパティにResponseErrorHandlerのbeanをインジェクションする。

クライアント処理の実装例

int retryCount = 0;
while (true) {

    responseEntity = restTemplate.exchange(requestEntity, User.class);

    if (responseEntity.getStatusCode() == HttpStatus.OK) { // (1)

        break;

    } else if (responseEntity.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE) { // (2)

        if (retryCount == retryMaxCount) {
            break;
        }

        retryCount++;

        if (logger.isWarnEnabled()) {
            logger.warn("An error ({}) occurred on the server. (The number of retries:{} Times)",
                responseEntity.getStatusCode(), retryCount);
        }

        try {
            Thread.sleep(retryWaitTimeCoefficient * retryCount);
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
        }

        //...
    }
}
項番 説明
(1)
上記の実装例では、エラー時にもResponseEntityを返すようにエラーハンドラを拡張しているので、返却されたResponseEntityからHTTPステータスコードを取得して、処理結果が正常であったか確認する必要がある。
(2)
エラー発生時も返却されたResponseEntityからHTTPステータスコードを取得して、その値に応じて処理を制御することができる。

5.2.2.7. 通信タイムアウトの設定

サーバとの通信に対してタイムアウト時間を指定したい場合は、以下のようなbean定義を行う。

bean定義ファイル(applicationContext.xml)の定義例

<bean id="clientHttpRequestFactory"
      class="org.springframework.http.client.SimpleClientHttpRequestFactory">
    <property name="connectTimeout" value="${api.connectTimeout: 2000}" /><!-- (1) -->
    <property name="readTimeout" value="${api.readTimeout: 2000}" /><!-- (2) -->
</bean>

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <constructor-arg ref="clientHttpRequestFactory" />
</bean>
項番 説明
(1)
connectTimeoutプロパティにサーバとの接続タイムアウト時間(ミリ秒)を設定する。
タイムアウト発生時はorg.springframework.web.client.ResourceAccessExceptionが発生する。
(2)
readTimeoutプロパティにレスポンスデータの読み込みタイムアウト時間(ミリ秒)を設定する。
タイムアウト発生時はResourceAccessExceptionが発生する。

Note

タイムアウト発生時の起因例外

ResourceAccessExceptionは起因例外をラップしており、接続タイムアウト及び読み込みタイムアウト発生時の起因例外は共にjava.net.SocketTimeoutExceptionである。 デフォルト実装(SimpleClientHttpRequestFactory)を使用した場合は、どちらのタイムアウトが発生したかを例外クラスの種類で区別できないという点を補足しておく。

なお、他のHttpRequestFactoryを使用した場合の動作は未検証であるため、起因例外が上記と異なる可能性がある。 他のHttpRequestFactoryを使用する場合は、タイムアウト時に発生する例外を把握した上で適切な例外ハンドリングを行うこと。

5.2.2.8. SSL自己署名証明書の使用

テスト環境などでSSL自己署名証明書を使用する場合は、以下のように実装する。

FactoryBeanの実装例

RestTemplateのBean定義で、コンストラクタの引数に渡す org.springframework.http.client.ClientHttpRequestFactoryを作成するための org.springframework.beans.factory.FactoryBeanを実装する。

import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;

public class RequestFactoryBean implements
        FactoryBean<ClientHttpRequestFactory> {

    private String keyStoreFileName;

    private char[] keyStorePassword;

    @Override
    public ClientHttpRequestFactory getObject() throws Exception {

        // (1)
        SSLContext sslContext = SSLContext.getInstance("TLS");

        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(this.getClass().getClassLoader()
                .getResourceAsStream(this.keyStoreFileName),
                this.keyStorePassword);

        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
                .getDefaultAlgorithm());
        kmf.init(ks, this.keyStorePassword);

        TrustManagerFactory tmf = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);

        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        // (2)
        HttpClient httpClient = HttpClientBuilder.create()
                .setSSLContext(sslContext).build();

        // (3)
        ClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
                httpClient);

        return factory;
    }

    @Override
    public Class<?> getObjectType() {
        return ClientHttpRequestFactory.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public void setKeyStoreFileName(String keyStoreFileName) {
        this.keyStoreFileName = keyStoreFileName;
    }

    public void setKeyStorePassword(char[] keyStorePassword) {
        this.keyStorePassword = keyStorePassword;
    }

}
項番 説明
(1)
後述のbean定義で指定されたキーストアファイルのファイル名とパスワードを元に、SSLコンテキストを作成する。
使用するSSL自己署名証明書のキーストアファイルは、クラスパス上に配置する。
(2)
作成したSSLコンテキストを利用する org.apache.http.client.HttpClientを作成する。
(3)
作成したHttpClientを利用する ClientHttpRequestFactoryを作成する。

Note

JDK 11より、TLS(Transport Layer Security) 1.3がデフォルトになった。

通信先のアプリケーションがTLS 1.2以前のバージョンにしか対応していない等の理由により、使用するTLSのバージョンをJVMレベルで変更するにはHTTP通信におけるTLS(Transport Layer Security) v1.3のサポートを参照されたい。

ただし、JVMレベルで設定してしまうと一つのクライアントアプリからTLS 1.2とTLS 1.3を利用した別々のアプリケーションに接続するような要件を実現することができない。 このような場合は、HttpClientごとに利用するTLSのバージョンを指定するような実装を検討されたい。

HttpClientおよび HttpClientBuilderを使用するためには、Apache HttpComponents HttpClient のライブラリが必要となる。 以下を pom.xmlに追加し、Apache HttpComponents HttpClient を依存ライブラリに追加する。

  • pom.xml
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

Note

上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 上記の依存ライブラリはterasoluna-gfw-parentが依存しているSpring Bootで管理されている。

bean定義ファイル(applicationContext.xml)の定義例

SSL自己署名証明書を使用したSSL通信を行う RestTemplateを定義する。

<bean id="httpsRestTemplate" class="org.springframework.web.client.RestTemplate">
    <constructor-arg>
        <bean class="com.example.restclient.RequestFactoryBean"><!-- (1) -->
            <property name="keyStoreFileName" value="${rscl.keystore.filename}" />
            <property name="keyStorePassword" value="${rscl.keystore.password}" />
        </bean>
    </constructor-arg>
</bean>
項番 説明
(1)
作成した RequestFactoryBeanRestTemplateのコンストラクタに指定する。
RequestFactoryBeanには、キーストアファイルのファイル名とパスワードを渡す。

RestTemplateの使用方法

RestTemplateの使い方については、SSL自己署名証明書を使用しない場合と同じである。

5.2.2.9. Basic認証

サーバがBasic認証を要求する場合は、以下のように実装する。このとき、Java標準のjava.util.Base64を使用する。

Basic認証の実装例

フィールド宣言部

@Value("${api.auth.username}")
String username;

@Value("${api.auth.password}")
String password;

メソッド内部

String plainCredentials = username + ":" + password; // (1)
String base64Credentials = Base64.getEncoder()
        .encodeToString(plainCredentials.getBytes(StandardCharsets.UTF_8)); // (2)

RequestEntity requestEntity = RequestEntity
      .get(uri)
      .header("Authorization", "Basic " + base64Credentials) // (3)
      .build();
項番 説明
(1)
ユーザ名とパスワードを「”:” 」でつなげる。
(2)
(1)をバイト配列に変換して、Base64エンコードする。
(3)
AuthorizationヘッダをBasic認証の資格情報を設定する。

Note

Spring Security 5より、org.springframework.security.crypto.codec.Base64は非推奨になったため、Java標準のjava.util.Base64に置き換えることを推奨する。

5.2.2.10. ファイルアップロード(マルチパートリクエスト)

RestTemplateを使用してファイルアップロード(マルチパートリクエスト)を行う場合は、以下のように実装する。

ファイルアップロードの実装例

MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap<>();//(1)
multiPartBody.add("file", new ClassPathResource("/uploadFiles/User.txt"));//(2)

RequestEntity<MultiValueMap<String, Object>> requestEntity = RequestEntity
        .post(uri)
        .contentType(MediaType.MULTIPART_FORM_DATA)//(3)
        .body(multiPartBody);//(4)
項番 説明
(1)
マルチパートリクエストとして送信するデータを格納するためにMultiValueMapを生成する。
(2)
パラメータ名をキーに指定して、アップロードするファイルをMultiValueMapに追加する。
上記例では、fileというパラメータ名を指定して、クラスパス上に配置されているファイルをアップロードファイルとして追加している。
(3)
Content-Typeヘッダのメディアタイプをmultipart/form-dataに設定する。
(4)
アップロードするファイルが格納されているMultiValueMapをリクエストボディに設定する。

Note

Spring Frameworkが提供するResourceクラスについて

Spring Frameworkはリソースを表現するインタフェースとしてorg.springframework.core.io.Resourceを提供しており、 ファイルをアップロードする際に使用することができる。

Resourceインタフェースの主な実装クラスは以下の通りである。

  • org.springframework.core.io.PathResource
  • org.springframework.core.io.FileSystemResource
  • org.springframework.core.io.ClassPathResource
  • org.springframework.core.io.UrlResource
  • org.springframework.core.io.InputStreamResource (ファイル名をサーバに連携できない)
  • org.springframework.core.io.ByteArrayResource (ファイル名をサーバに連携できない)

5.2.2.11. ファイルダウンロード

RestTeamplateを使用してファイルダウンロードを行う場合は、以下のように実装する。

ファイルダウンロードの実装例(ファイルサイズが小さい場合)

RequestEntity requestEntity = RequestEntity
        .get(uri)
        .build();

ResponseEntity<byte[]> responseEntity =
        restTemplate.exchange(requestEntity, byte[].class);//(1)

byte[] downloadContent = responseEntity.getBody();//(2)
項番 説明
(1)
ダウンロードファイルを指定したデータ型で扱う。ここでは、バイト配列を指定。
(2)
レスポンスボディからダウンロードしたファイルのデータを取得する。

Warning

サイズの大きいファイルをダウンロードする際の注意点

サイズの大きなファイルをデフォルトで登録されているHttpMessageConverterを使用して byte配列で取得すると、 java.lang.OutOfMemoryErrorが発生する可能性がある。 そのため、サイズの大きなファイルをダウンロードしたい場合は、レスポンスから InputStreamを取得して、ダウンロードデータを少しずつファイルに書き出す必要がある。

ファイルダウンロードの実装例(ファイルサイズが大きい場合)

// (1)
final ResponseExtractor<ResponseEntity<File>> responseExtractor =
        new ResponseExtractor<ResponseEntity<File>>() {

    // (2)
    @Override
    public ResponseEntity<File> extractData(ClientHttpResponse response)
            throws IOException {

        File rcvFile = File.createTempFile("rcvFile", "zip");

        FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile));

        return ResponseEntity.status(response.getStatusCode())
                .headers(response.getHeaders()).body(rcvFile);
    }

};

// (3)
ResponseEntity<File> responseEntity = this.restTemplate.execute(targetUri,
        HttpMethod.GET, null, responseExtractor);
if (HttpStatus.OK.equals(responseEntity.getStatusCode())) {
    File getFile = responseEntity.getBody();

    .....

}
項番 説明
(1)
RestTemplate#executeで取得されたレスポンスから、RestTemplate#executeの戻り値を作成するための処理を作成する。
(2)
レスポンスボディ(InputStream)からデータを読込み、ファイルを作成する。
作成したファイルとHTTPヘッダ、ステータスコードを ResponseEntity<File>に格納して返却する。
(3)
RestTemplate#executeを使用して、ファイルのダウンロードを行う。

ファイルダウンロードの実装例(ファイルサイズが大きい場合(ResponseEntityを使わない例))

ステータスコードの判定やHTTPヘッダの参照等が不要な場合は、 以下のようにResponseEntityではなく Fileを返却すればよい。

final ResponseExtractor<File> responseExtractor = new ResponseExtractor<File>() {

    @Override
    public File extractData(ClientHttpResponse response)
            throws IOException {

        File rcvFile = File.createTempFile("rcvFile", "zip");

        FileCopyUtils.copy(response.getBody(), new FileOutputStream(
                rcvFile));

        return rcvFile;
    }

};

File getFile = this.restTemplate.execute(targetUri, HttpMethod.GET,
        null, responseExtractor);
.....

5.2.2.12. RESTfulなURL(URIテンプレート)を扱う方法と実装例

RESTfulなURLを扱うには、URIテンプレートを使用して実装を行えばよい。

getForObjectメソッドでの使用例

フィールド宣言部

@Value("${api.serverUrl}/api/users/{userId}") // (1)
String uriStr;

メソッド内部

User user = restTemplate.getForObject(uriStr, User.class, "0001"); // (2)
項番 説明
(1)
URIテンプレートの変数{userId}は、RestTeamplateの使用時に指定の値に変換される。
(2)
URIテンプレートの変数1つ目が getForObjectメソッドの第3引数に指定した値で置換され、『http://localhost:8080/api/users/0001』として処理される。

exchangeメソッドでの使用例

@Value("${api.serverUrl}/api/users/{action}") // (1)
String uriStr;

メソッド内部

URI targetUri = UriComponentsBuilder.fromUriString(uriStr).
        buildAndExpand("create").toUri(); //(2)

User user = new User();

//...

RequestEntity<User> requestEntity = RequestEntity
        .post(targetUri)
        .body(user);

ResponseEntity<User> responseEntity = restTemplate.exchange(requestEntity, User.class);
項番 説明
(1)
URIテンプレートの変数{action}は、RestTeamplateの使用時に指定の値に変換される。
(2)
UriComponentsBuilderを使用することで、URIテンプレートの変数1つ目が buildAndExpandの引数で指定した値に置換され、『http://localhost:8080/api/users/create』のURIが作成される。
詳細はUriComponentsBuilderのJavadocを参照されたい。

5.2.3. How to extend

本節では、RestTemplateの拡張方法について説明する。

5.2.3.1. 任意のHttpMessageConverterを登録する方法

デフォルトで登録されている HttpMessageConverterで電文変換の要件を満たせない場合は、任意のHttpMessageConverterを登録することができる。 ただし、デフォルトで登録されていたHttpMessageConverterは削除されるので、必要なHttpMessageConverterをすべて個別に登録する必要がある。

bean定義ファイル(applicationContext.xml)の定義例

<bean id="jaxb2CollectionHttpMessageConverter"
      class="org.springframework.http.converter.xml.Jaxb2CollectionHttpMessageConverter" /> <!-- (1) -->

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="messageConverters"> <!-- (2) -->
        <list>
            <ref bean="jaxb2CollectionHttpMessageConverter" />
        </list>
    </property>
</bean>
項番 説明
(1)
登録するHttpMessageConverterの実装クラスをbean定義する。
(2)
messageConvertersプロパティに登録するHttpMessageConverterのbeanをインジェクションする。

5.2.3.2. 共通処理の適用(ClientHttpRequestInterceptor

ClientHttpRequestInterceptorを使用することで、サーバとの通信処理の前後に任意の処理を実行させることができる。
ここでは、ロギング処理 と、Basic認証用のリクエストヘッダ設定処理 を適用する方法を紹介する。

5.2.3.2.1. ロギング処理

サーバとの通信ログを出力したい場合は、以下のような実装を行う。

通信ログ出力の実装例

package com.example.restclient;

import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

public class LoggingInterceptor implements ClientHttpRequestInterceptor { //(1)

    private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body,
            ClientHttpRequestExecution execution) throws IOException {

        if (logger.isInfoEnabled()) {
            String requestBody = new String(body, StandardCharsets.UTF_8);

            logger.info("Request Header {}", request.getHeaders()); //(2)
            logger.info("Request Body {}", requestBody);
        }

        ClientHttpResponse response = execution.execute(request, body); //(3)

        if (logger.isInfoEnabled()) {
            logger.info("Response Header {}", response.getHeaders()); // (4)
            logger.info("Response Status Code {}", response.getStatusCode()); // (5)
        }

        return response; // (6)
    }

}
項番 説明
(1)
ClientHttpRequestInterceptorインタフェースを実装する。
(2)
リクエストする前に行いたい共通処理を実装する。
上記の実装例では、リクエストヘッダーとリクエストボディの内容をログに出力している。
(3)
interceptメソッドの引数として受け取ったClientHttpRequestExecutionexecuteメソッドを実行し、リクエストの送信を実行する。
(4)
レスポンスを受け取った後に行いたい共通処理を実装する。
上記の実装例では、レスポンスヘッダの内容をログに出力している。
(5)
(4)と同様に、ステータスコードの内容をログに出力している。
(6)
(3)で受信したレスポンスをリターンする。

bean定義ファイル(applicationContext.xml)の定義例

<!-- (1) -->
<bean id="loggingInterceptor" class="com.example.restclient.LoggingInterceptor" />
項番 説明
(1)
ClientHttpRequestInterceptorの実装クラスのbean定義を行う。

5.2.3.2.2. Basic認証用のリクエストヘッダ設定処理

サーバにアクセスするためにBasic認証用のリクエストヘッダを設定する必要がある場合は、以下のようなbean定義を行う。

bean定義ファイル(applicationContext.xml)の定義例

<!-- (1) -->
<bean id="basicAuthInterceptor" class="org.springframework.http.client.support.BasicAuthorizationInterceptor">
    <constructor-arg index="0" value="${api.auth.username}" /><!-- (2) -->
    <constructor-arg index="1" value="${api.auth.password}" /><!-- (3) -->
</bean>
項番 説明
(1)
ClientHttpRequestInterceptorインタフェースを実装したBasicAuthorizationInterceptorのbean定義を行う。
(2)
コンストラクタの第一引数にユーザ名を設定する。
(3)
コンストラクタの第二引数にパスワードを設定する。

5.2.3.2.3. ClientHttpRequestInterceptorの適用

RestTemplateに作成したClientHttpRequestInterceptorを適用する場合は、以下のようなbean定義を行う。

bean定義ファイル(applicationContext.xml)の定義例

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="interceptors"><!-- (1) -->
        <list>
            <ref bean="basicAuthInterceptor" />
            <ref bean="loggingInterceptor" />
        </list>
    </property>
</bean>
項番 説明
(1)
interceptorsプロパティにClientHttpRequestInterceptorのbeanをインジェクションする。
複数のbeanをインジェクションした場合は、リストの先頭から順にチェーン実行される。
上記の例だと、BasicAuthorizationInterceptor -> LoggingInterceptor -> ClientHttpRequest の順番でリクエスト前の処理が実行される。(レスポンス後の処理は順番が逆転する)

5.2.3.3. 非同期リクエスト

非同期リクエストを行う場合は、org.springframework.web.client.AsyncRestTemplateを使用する。

Note

Spring Framework 5.0から、Spring Web Reactiveの機能としてRestTemplateの後継となるWebClientが提供された。これに伴いAsyncRestTemplateは非推奨となった。将来のバージョンではRestTemplateも非推奨となる予定であり、今後は主要な新機能の追加はされずメンテナンスのみとなることがアナウンスされている。

Macchinetta Server Framework (1.x)ではSpring Web Reactiveをサポートしていないため、WebClientへの移行は推奨していない。


5.2.3.3.1. AsyncRestTemplateのbean定義

AsyncRestTemplateのbean定義を行う。

bean定義ファイル(applicationContext.xml)の定義例

<bean id="asyncRestTemplate" class="org.springframework.web.client.AsyncRestTemplate" /> <!-- (1) -->
項番 説明
(1)
AsyncRestTemplateをデフォルト設定のまま利用する場合は、デフォルトコンストラクタを使用してbeanを登録する。
デフォルト設定の場合、AsyncRestTemplateorg.springframework.http.client.AsyncClientHttpRequestFactoryには、org.springframework.core.task.AsyncListenableTaskExecutorとしてorg.springframework.core.task.SimpleAsyncTaskExecutorが設定された SimpleClientHttpRequestFactoryが設定される。

Note

AsyncRestTemplateのカスタマイズ方法

デフォルトで設定されるSimpleAsyncTaskExecutorは、スレッドプールを使わずにスレッドを生成しており、 スレッドの同時実行数に制限は無い。 そのため、同時に使用するスレッド数が非常に大きい場合はOutOfMemoryErrorが発生する可能性がある。

AsyncRestTemplateのコンストラクタにorg.springframework.core.task.AsyncListenableTaskExecutorインターフェースのBeanを設定することで、スレッドプールの設定を行える。 下記はorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutorを設定する例である。

<!-- (1) -->
<bean id="asyncTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="5" /> <!-- (2) -->
    <property name="queueCapacity" value="25" /> <!-- (3) -->
    <property name="maxPoolSize" value="10" /> <!-- (4) -->
</bean>

<!-- (5) -->
<bean id="asyncRestTemplate" class="org.springframework.web.client.AsyncRestTemplate" >
    <constructor-arg index="0" ref="asyncTaskExecutor" />
</bean>
項番 説明
(1)
AsyncTaskExecutorのbean定義を行う。
ThreadPoolTaskExecutorを使うことで、スレッドプールを使ったスレッド運用が行われる。
(2)
corePoolSizeプロパティを設定することで、通常使用するスレッド数のカスタマイズを行える。
タスク実行時にプール内のスレッド数がcorePoolSize未満の場合、アイドル状態のスレッドが存在していてもプール内に新しいスレッドが作成される。
デフォルト値は1
(3)
queueCapacityプロパティを設定すると、キュー容量のカスタマイズを行える。
corePoolSizeを超えたリクエストは、queueCapacityまでキューイングされる。
デフォルト値はInteger.MAX_VALUE
(4)
maxPoolSizeプロパティを設定することで、最大スレッド数のカスタマイズを行える。
リクエストがqueueCapacityを超えた場合、maxPoolSizeまで新しいスレッドを作成する。
デフォルト値はInteger.MAX_VALUE
キュー容量、 スレッド数が共に飽和状態の場合、新しいタスクは拒否される。
(5)
AsyncRestTemplateのbean定義を行う。
ThreadPoolTaskExecutorを引数に指定するコンストラクタを使用してbeanを登録する。

本ガイドラインでは、タスク実行処理をカスタマイズする実装例のみを紹介するが、 AsyncRestTemplateは、HTTP通信処理もカスタマイズ出来る。 詳細はAsyncRestTemplateのJavadocを参照されたい。

また、ThreadPoolTaskExecutorについても、スレッドプールサイズ以外のカスタマイズが出来る。 詳細はThreadPoolTaskExecutorのJavadocを参照されたい。

5.2.3.3.2. 非同期リクエストの実装

非同期リクエストの実装例

フィールド宣言部

@Inject
AsyncRestTemplate asyncRestTemplate;

メソッド内部

ListenableFuture<ResponseEntity<User>> responseEntity =
        asyncRestTemplate.getForEntity(uri, User.class); // (1)

responseEntity.addCallback(new ListenableFutureCallback<ResponseEntity<User>>() { // (2)
    @Override
    public void onSuccess(ResponseEntity<User> entity) {
        //...
    }

    @Override
    public void onFailure(Throwable t) {
      //...
    }
});
項番 説明
(1)
AsyncRestTemplateの各メソッドを使用して、非同期リクエストを送信する。
上記の実装例では、getForEntityメソッドを使用している。
戻り値は、org.springframework.util.concurrent.ListenableFutureにラップされた、ResponseEntityとなっている。
各メソッドの使用方法は、RestTemplateと似たものとなっている。
(2)
ListenableFutureorg.springframework.util.concurrent.ListenableFutureCallbackを登録して、レスポンスが返ってきた際の処理を実装する。
成功のレスポンスが返ってきた場合の処理はonSuccessメソッドに、エラーが発生した場合の処理はonFailureメソッドに実装する。

5.2.3.3.3. 非同期リクエストの共通処理の実装

org.springframework.http.client.AsyncClientHttpRequestInterceptorを使用することで、サーバとの通信処理の前後に任意の処理を実行させることができる。

ここでは、ロギング処理の実装例を紹介する。

通信ログ出力の実装例

package com.example.restclient;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.AsyncClientHttpRequestExecution;
import org.springframework.http.client.AsyncClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;

public class AsyncLoggingInterceptor implements
                                     AsyncClientHttpRequestInterceptor { // (1)
    private static final Logger logger = LoggerFactory.getLogger(
            AsyncLoggingInterceptor.class);

    @Override
    public ListenableFuture<ClientHttpResponse> intercept(HttpRequest request,
            byte[] body,
            AsyncClientHttpRequestExecution execution) throws IOException {
        // (2)
        if (logger.isInfoEnabled()) {
            String requestBody = new String(body, StandardCharsets.UTF_8);

            logger.info("Request Header {}", request.getHeaders());
            logger.info("Request Body {}", requestBody);
        }

        // (3)
        ListenableFuture<ClientHttpResponse> future = execution.executeAsync(
                request, body);
        if (logger.isInfoEnabled()) {
            // (4)
            future.addCallback(new ListenableFutureCallback<ClientHttpResponse>() {

                @Override
                public void onSuccess(ClientHttpResponse response) {
                    try {
                        logger.info("Response Header {}", response
                                .getHeaders());
                        logger.info("Response Status Code {}", response
                                .getStatusCode());
                    } catch (IOException e) {
                        logger.warn("I/O Error", e);
                    }
                }

                @Override
                public void onFailure(Throwable e) {
                    logger.info("Communication Error", e);
                }
            });
        }

        return future; // (5)
    }
}
項番 説明
(1)
AsyncClientHttpRequestInterceptorインタフェースを実装する。
(2)
非同期リクエストを送信する前に実行する処理を実装する。
上記の実装例では、リクエストヘッダとリクエストボディの内容をログに出力している。
(3)
interceptメソッドの引数として受け取ったAsyncClientHttpRequestExecutionexecuteAsyncメソッドを使用して、非同期リクエストを送信する。
(4)
(3)で受け取ったListenableFutureorg.springframework.util.concurrent.ListenableFutureCallbackを登録して、レスポンスが返ってきた際の処理を実装する。
レスポンスが返却された場合、onSuccessメソッドが呼び出される。
また、非同期リクエスト時に例外が発生した場合、onFailureメソッドが呼び出される。以下に具体例を示す。
  • 指定したホストに接続できない(ConnectException
  • レスポンスデータの読み込みタイムアウトが発生した(SocketTimeoutException
(5)
(3)で受け取ったListenableFutureをリターンする。

bean定義ファイル(applicationContext.xml)の定義例

<!-- (1) -->
<bean id="asyncLoggingInterceptor" class="com.example.restclient.AsyncLoggingInterceptor" />

<bean id="asyncRestTemplate" class="org.springframework.web.client.AsyncRestTemplate">
    <property name="interceptors"><!-- (2) -->
        <list>
            <ref bean="asyncLoggingInterceptor" />
        </list>
    </property>
</bean>
項番 説明
(1)
AsyncClientHttpRequestInterceptorの実装クラスのbean定義を行う。
(2)
interceptorsプロパティにAsyncClientHttpRequestInterceptorのbeanをインジェクションする。
複数のbeanをインジェクションした場合は、RestTemplateと同様にリストの先頭から順にチェーン実行される。

5.2.4. Appendix

5.2.4.1. HTTP Proxyサーバの設定方法

サーバへアクセスする際にHTTP Proxyサーバを経由する必要がある場合は、システムプロパティやJVM起動引数、またはRestTemplateのBean定義にてHTTP Proxyサーバの設定が必要となる。 システムプロパティやJVM起動引数に設定した場合、アプリケーション全体に影響を与えてしまうため、RestTemplate毎にHTTP Proxyサーバの設定を行う例を紹介する。

RestTemplate毎のHTTP Proxyサーバの設定は、ClientHttpRequestFactoryインタフェースのデフォルト実装であるSimpleClientHttpRequestFactoryに付与することが可能である。 ただしSimpleClientHttpRequestFactoryでは資格情報を設定することはできないため、Proxy認証を行う場合はHttpComponentsClientHttpRequestFactoryを使用する。 HttpComponentsClientHttpRequestFactoryは、Apache HttpComponents HttpClientを用いてリクエストを生成するClientHttpRequestFactoryインタフェースの実装クラスである。

5.2.4.1.1. SimpleClientHttpRequestFactoryを使用したHTTP Proxyサーバの設定方法

資格情報が不要なHTTP Proxyサーバの接続先の指定については、RestTemplateがデフォルトで使用するSimpleClientHttpRequestFactoryで指定することが可能である。

Bean定義ファイル

<!-- (1) -->
<bean id="inetSocketAddress" class="java.net.InetSocketAddress" >
    <constructor-arg index="0" value="${rscl.http.proxyHost}" />    <!-- (2) -->
    <constructor-arg index="1" value="${rscl.http.proxyPort}" />    <!-- (3) -->
</bean>

<!-- (4) -->
<bean id="simpleClientRestTemplate" class="org.springframework.web.client.RestTemplate" >
    <constructor-arg>
        <!-- (5) -->
        <bean class="org.springframework.http.client.SimpleClientHttpRequestFactory">
            <!-- (6) -->
            <property name="proxy">
                <bean class="java.net.Proxy" >
                    <!-- (7) -->
                    <constructor-arg index="0">
                        <util:constant static-field="java.net.Proxy.Type.HTTP"/>
                    </constructor-arg>
                    <constructor-arg index="1" ref="inetSocketAddress"/>
                </bean>
            </property>
        </bean>
    </constructor-arg>
</bean>
項番 説明
(1)
java.net.InetSocketAddressにHTTP Proxyサーバの設定を行う。
(2)
InetSocketAddressのコンストラクタの第一引数に、プロパティファイルに設定されたキーrscl.http.proxyHostの値をHTTP Proxyサーバのホスト名として設定する。
(3)
InetSocketAddressのコンストラクタの第二引数に、プロパティファイルに設定されたキーrscl.http.proxyPortの値をHTTP Proxyサーバのポート番号として設定する。
(4)
RestTemplateのBean定義を行う。
(5)
RestTemplateのコンストラクタの引数に、SimpleClientHttpRequestFactoryを設定する。
(6)
SimpleClientHttpRequestFactoryproxyプロパティにjava.net.Proxyを設定する。
(7)
Proxyのコンストラクタの引数に、java.net.Proxy.Type.HTTPと生成したInetSocketAddressを設定する。

5.2.4.1.2. HttpComponentsClientHttpRequestFactoryを使用したHTTP Proxyサーバの設定方法

5.2.4.1.2.1. HTTP Proxyサーバの指定方法

資格情報が必要なHTTP Proxyサーバの接続先の指定は、RestTemplateに対して、HttpComponentsClientHttpRequestFactoryを使用し指定する。

pom.xml

<!-- (1) -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
項番 説明
(1)
HttpComponentsClientHttpRequestFactory内で使用するApache HTTP Clientを使用するために、Apache HttpComponents Clientpom.xml の依存ライブラリに追加する。

Note

上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 上記の依存ライブラリはterasoluna-gfw-parentが依存しているSpring Bootで管理されている。

Bean定義ファイル

<!-- (1) -->
<bean id="proxyHttpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder" factory-method="create" >
    <!-- (2) -->
    <property name="proxy">
        <bean class="org.apache.http.HttpHost" >
            <constructor-arg index="0" value="${rscl.http.proxyHost}" />    <!-- (3) -->
            <constructor-arg index="1" value="${rscl.http.proxyPort}" />    <!-- (4) -->
        </bean>
    </property>
</bean>

<!-- (5) -->
<bean id="proxyRestTemplate" class="org.springframework.web.client.RestTemplate" >
    <constructor-arg>
        <!-- (6) -->
        <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
            <!-- (7) -->
            <constructor-arg>
                <bean factory-bean="proxyHttpClientBuilder" factory-method="build" />
            </constructor-arg>
        </bean>
    </constructor-arg>
</bean>
項番 説明
(1)
org.apache.http.impl.client.HttpClientBuilderを使用し、org.apache.http.client.HttpClientの設定を行う。
(2)
HttpClientBuilderproxyプロパティに、HTTP Proxyサーバの設定を行ったorg.apache.http.HttpHostを設定する。
(3)
HttpHostのコンストラクタの第一引数に、プロパティファイルに設定されたキーrscl.http.proxyHostの値をHTTP Proxyサーバのホスト名として設定する。
(4)
HttpHostのコンストラクタの第二引数に、プロパティファイルに設定されたキーrscl.http.proxyPortの値をHTTP Proxyサーバのポート番号として設定する。
(5)
RestTemplateのBean定義を行う。
(6)
RestTemplateのコンストラクタの引数に、HttpComponentsClientHttpRequestFactoryを設定する。
(7)
HttpComponentsClientHttpRequestFactoryのコンストラクタの引数に、HttpClientBuilderから生成したHttpClientを設定する。
5.2.4.1.2.2. HTTP Proxyサーバの資格情報の指定方法

HTTP Proxyサーバにアクセスする際に資格情報(ユーザ名とパスワード)が必要な場合は、org.apache.http.impl.client.BasicCredentialsProviderを使用し資格情報を設定する。

BasicCredentialsProvidersetCredentialsメソッドが引数を2つ取るため、セッターインジェクションを利用してBeanを生成することができない。このため、org.springframework.beans.factory.FactoryBeanを利用してBeanを生成する。

FactoryBeanクラス

import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Value;

// (1)
public class BasicCredentialsProviderFactoryBean implements FactoryBean<BasicCredentialsProvider> {

    // (2)
    @Value("${rscl.http.proxyHost}")
    String host;

    // (3)
    @Value("${rscl.http.proxyPort}")
    int port;

    // (4)
    @Value("${rscl.http.proxyUserName}")
    String userName;

    // (5)
    @Value("${rscl.http.proxyPassword}")
    String password;

    @Override
    public BasicCredentialsProvider getObject() throws Exception {

        // (6)
        AuthScope authScope = new AuthScope(this.host, this.port);

        // (7)
        UsernamePasswordCredentials usernamePasswordCredentials =
                new UsernamePasswordCredentials(this.userName, this.password);

        // (8)
        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(authScope, usernamePasswordCredentials);

        return credentialsProvider;
    }

    @Override
    public Class<?> getObjectType() {
        return BasicCredentialsProvider.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}
項番 説明
(1)
org.springframework.beans.factory.FactoryBeanを実装したBasicCredentialsProviderFactoryBeanクラスを定義する。
Beanの型にBasicCredentialsProviderを設定する。
(2)
プロパティファイルに設定されたキーrscl.http.proxyHostの値をHTTP Proxyサーバのホスト名として、インスタンス変数に設定する。
(3)
プロパティファイルに設定されたキーrscl.http.proxyPortの値をHTTP Proxyサーバのポート番号として、インスタンス変数に設定する。
(4)
プロパティファイルに設定されたキーrscl.http.proxyUserNameの値をHTTP Proxyサーバのユーザ名として、インスタンス変数に設定する。
(5)
プロパティファイルに設定されたキーrscl.http.proxyPasswordの値をHTTP Proxyサーバのパスワードとして、インスタンス変数に設定する。
(6)
org.apache.http.auth.AuthScope を作成し資格情報のスコープを設定する。この例は、HTTP Proxyサーバのホスト名とポート番号を指定したものである。その他の設定方法については、AuthScope (Apache HttpClient API)を参照されたい。
(7)
org.apache.http.auth.UsernamePasswordCredentials を作成し資格情報を設定する。
(8)
org.apache.http.impl.client.BasicCredentialsProviderを作成し、setCredentialsメソッドを使用し、資格情報のスコープと資格情報を設定する。

Bean定義ファイル

<bean id="proxyHttpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder" factory-method="create">
    <!-- (1) -->
    <property name="defaultCredentialsProvider">
        <bean class="com.example.restclient.BasicCredentialsProviderFactoryBean" />
    </property>
    <property name="proxy">
        <bean id="proxyHost" class="org.apache.http.HttpHost">
            <constructor-arg index="0" value="${rscl.http.proxyHost}" />
            <constructor-arg index="1" value="${rscl.http.proxyPort}" />
        </bean>
    </property>
</bean>

<bean id="proxyRestTemplate" class="org.springframework.web.client.RestTemplate">
    <constructor-arg>
        <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
            <constructor-arg>
                <bean factory-bean="proxyHttpClientBuilder" factory-method="build" />
            </constructor-arg>
        </bean>
    </constructor-arg>
</bean>
項番 説明
(1)
HttpClientBuilderdefaultCredentialsProviderプロパティに、BasicCredentialsProviderを設定する。
BasicCredentialsProviderは、FactoryBeanを実装したBasicCredentialsProviderFactoryBeanを使用しBeanを作成する。

5.2.4.2. JSONでJSR-310 Date and Time APIを使う場合の設定

リソースを表現するJavaBean(Resourceクラス)のプロパティとしてJSR-310 Date and Time APIを使用する場合の設定は 「JSR-310 Date and Time API / Joda Timeを使う場合の設定 」を参照されたい。