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)にアクセスしているかを説明する。
項番 | コンポーネント | 説明 |
---|---|---|
(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
の実装クラスが登録される。
項番 | クラス名 | 説明 | サポート型 |
---|---|---|---|
(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 )をサポートする。
デフォルトで登録されるパートデータ変換用の
HttpMessageConveter は、AllEncompassingFormHttpMessageConverterと FormHttpMessageConverterのソースを参照されたい。なお、任意のHttpMessageConverter を登録することもできる。 |
MultiValueMap [3] |
Note
AllEncompassingFormHttpMessageConverterのメディアタイプがmultipart/form-dataの場合について
メディアタイプがmultipart/form-data
の場合、「MultiValueMap
オブジェクト から HTTPボディ」への変換は可能だが、
「HTTPボディ から MultiValueMap
オブジェクト」への変換は現状サポートされていない。
よって、「HTTPボディ から MultiValueMap
オブジェクト」への変換を行いたい場合は、独自に実装する必要がある。
項番 | クラス名 | 説明 | サポート型 |
---|---|---|---|
(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
は、サーバとの通信処理を行うクラス(ClientHttpRequest
と ClientHttpResponse
インタフェースの実装クラス)を解決する役割を担っている。
なお、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ライブラリを追加する。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を登録する。
なお、HttpMessageConverter
、ResponseErrorHandler
、ClientHttpRequestInterceptor
のカスタマイズ方法については、
を参照されたい。
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.RequestEntity
とexchange
メソッドを使用する。
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)
|
RequestEntity のget メソッドを使用し、GETリクエスト用のリクエストビルダを生成する。パラメータにURIを設定する。
|
(2)
|
RequestEntity.HeadersBuilder のbuild メソッドを使用し、RequestEntity オブジェクトを作成する。 |
(3)
|
exchange メソッドを使用し、リクエストを送信する。第二引数に、レスポンスデータの型を指定する。レスポンスは、
ResponseEntity<T> になる。型パラメータに、レスポンスデータの型を設定する。 |
(4)
|
getBody メソッドを使用し、レスポンスボディのデータを取得する。 |
Note
RequestEntityとは
RequestEntity
はHTTPリクエストを表すクラスで、接続URI、HTTPメソッド、リクエストヘッダ、リクエストボディを設定することができる。
詳細はRequestEntityのJavadocを参照されたい。
なお、リクエストヘッダの設定方法については、リクエストヘッダの設定 を参照されたい。
5.2.2.3. POSTリクエストの送信¶
RestTemplate
は、POSTリクエストを行うためのメソッドを複数提供している。
- 通常は、
postForObject
、postForEntity
を使用する。 - 任意のヘッダを設定するなどリクエストに細かい設定を行いたい場合は、
RequestEntity
とexchange
メソッドを使用する。
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.BodyBuilder のbody メソッドを使用し、RequestEntity オブジェクトを作成する。パラメータにリクエストボディに変換するJavaオブジェクトを設定する。
|
(4)
|
exchange メソッドを使用し、リクエストを送信する。 |
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. リクエストヘッダの設定¶
RequestEntity
とexchange
メソッドを使用すると、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.BodyBuilder のcontentType メソッドを使用し、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.HeadersBuilder のaccept メソッドを使用して、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.HeadersBuilder のheader メソッドを使用してリクエストヘッダの名前と値を設定する。上記の実装例では、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 (log.isInfoEnabled()) {
log.info("Success({}) ", responseEntity.getStatusCode());
}
break;
} catch (HttpServerErrorException e) { // (1)
if (retryCount == retryMaxCount) {
throw e;
}
retryCount++;
if (log.isWarnEnabled()) {
log.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 (log.isWarnEnabled()) {
log.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)
|
作成した
RequestFactoryBean を RestTemplate のコンストラクタに指定する。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
を使用することで、サーバとの通信処理の前後に任意の処理を実行させることができる。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 (log.isInfoEnabled()) {
String requestBody = new String(body, StandardCharsets.UTF_8);
log.info("Request Header {}", request.getHeaders()); //(2)
log.info("Request Body {}", requestBody);
}
ClientHttpResponse response = execution.execute(request, body); //(3)
if (log.isInfoEnabled()) {
log.info("Response Header {}", response.getHeaders()); // (4)
log.info("Response Status Code {}", response.getStatusCode()); // (5)
}
return response; // (6)
}
}
項番 | 説明 |
---|---|
(1)
|
ClientHttpRequestInterceptor インタフェースを実装する。 |
(2)
|
リクエストする前に行いたい共通処理を実装する。
上記の実装例では、リクエストヘッダーとリクエストボディの内容をログに出力している。
|
(3)
|
intercept メソッドの引数として受け取ったClientHttpRequestExecution のexecute メソッドを実行し、リクエストの送信を実行する。 |
(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 for Java (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を登録する。デフォルト設定の場合、
AsyncRestTemplate のorg.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="maxPoolSize" value="100" /> </bean> <!-- (2) --> <bean id="asyncRestTemplate" class="org.springframework.web.client.AsyncRestTemplate" > <constructor-arg index="0" ref="asyncTaskExecutor" /> </bean>
項番 説明 (1)AsyncTaskExecutor
のbean定義を行う。ThreadPoolTaskExecutor
を使うことで、スレッドプールを使ったスレッド運用が行われる。また、maxPoolSize
プロパティを設定することで、スレッド数の制御が行える。 (2)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)
|
ListenableFuture にorg.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 (log.isInfoEnabled()) {
String requestBody = new String(body, StandardCharsets.UTF_8);
log.info("Request Header {}", request.getHeaders());
log.info("Request Body {}", requestBody);
}
// (3)
ListenableFuture<ClientHttpResponse> future = execution.executeAsync(
request, body);
if (log.isInfoEnabled()) {
// (4)
future.addCallback(new ListenableFutureCallback<ClientHttpResponse>() {
@Override
public void onSuccess(ClientHttpResponse response) {
try {
log.info("Response Header {}", response
.getHeaders());
log.info("Response Status Code {}", response
.getStatusCode());
} catch (IOException e) {
log.warn("I/O Error", e);
}
}
@Override
public void onFailure(Throwable e) {
log.info("Communication Error", e);
}
});
}
return future; // (5)
}
}
項番 | 説明 |
---|---|
(1)
|
AsyncClientHttpRequestInterceptor インタフェースを実装する。 |
(2)
|
非同期リクエストを送信する前に実行する処理を実装する。
上記の実装例では、リクエストヘッダとリクエストボディの内容をログに出力している。
|
(3)
|
intercept メソッドの引数として受け取ったAsyncClientHttpRequestExecution のexecuteAsync メソッドを使用して、非同期リクエストを送信する。 |
(4)
|
(3)で受け取った
ListenableFuture にorg.springframework.util.concurrent.ListenableFutureCallback を登録して、レスポンスが返ってきた際の処理を実装する。レスポンスが返却された場合、
onSuccess メソッドが呼び出される。また、非同期リクエスト時に例外が発生した場合、
onFailure メソッドが呼び出される。以下に具体例を示す。
|
(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)
|
SimpleClientHttpRequestFactory のproxy プロパティに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 Client を pom.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)
|
HttpClientBuilder のproxy プロパティに、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
を使用し資格情報を設定する。
BasicCredentialsProvider
のsetCredentials
メソッドが引数を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)
|
HttpClientBuilder のdefaultCredentialsProvider プロパティに、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を使う場合の設定 」を参照されたい。