5.2. RESTクライアント(HTTPクライアント)¶
5.2.1. Overview¶
RestTemplateはSpring Frameworkにて将来的に廃止されることが予定されており、RestTemplateの移行先としてRestClientが紹介されている。RestClientによる同期通信の実装を推奨している。RestClientを用いた設定や利用方法については、TERASOLUNA Server Framework for Java (5.x) 5.11.x以降のガイドラインを参照されたい。5.2.1.1. RestTemplateを利用したクライアント実装¶
RestTemplateは、Spring Framework 3.0から導入されているRESTクライアント実装であり、REST APIへの同期通信をサポートしたクライアント実装である。AsyncRestTemplateが削除されたことにより、WebClientを利用した実装が推奨されており、現在は同期通信のみをサポートしている。RestTemplateの移行先としてはRestClientが紹介されている。RestTemplateが非推奨となった経緯は、The state of HTTP clients in Springを参照されたい。RestTemplate関する記載を残しているが、今後、RESTクライアントの実装を行う際はRestClientの利用を推奨する。RestClientにおけるRESTクライアントの実現方法について記載しているので、必要に応じて参照してほしい。RestTemplateを利用したクライアントアプリケーションが、どのようにREST APIを公開しているサーバサイドアプリケーションにアクセスし、RESTによる通信を実現するかを示す。RestTemplate内で利用されるクラスの詳細な説明は、「RestTemplateの構成要素と利用上のポイント」にてまとめて説明を行っているのでこちらを参照されたい。
項番 |
処理 |
説明 |
|---|---|---|
(1)
|
アプリケーションからの呼び出し
|
RestTemplateのメソッドを実行し、REST API(Web API)の呼び出し依頼を行う。 |
(2)
|
URL生成
|
UriBuilderFactoryの実装クラスが、UriBuilderの生成を行う。生成した
UriBuilderを使用して、URLのパラメータを置換し送信先のURLの組み立てを行う。なお、
UriBuilderFactoryの実装クラスを指定していない場合は、デフォルト実装クラスであるDefaultUriBuilderFactoryが使用される。 |
(3)
|
リクエスト生成・初期化
|
ClientHttpRequestFactoryの実装クラスがClientHttpRequestの生成を行う。なお、
ClientHttpRequestFactoryの実装クラスを指定していない場合は、デフォルト実装クラスであるSimpleClientHttpRequestFactoryが使用される。ClientHttpRequest生成後、ClientHttpRequestInitializerを設定している場合、ClientHttpRequestInitializerの実装クラスを使用してClientHttpRequest(リクエスト)の初期化処理を行う。「リクエストの生成(ClientHttpRequestFactory)」にて詳しく説明を行うが、 Macchinetta Server Framework (1.x) では、
ClientHttpRequestFactoryの実装クラスはHttpComponentsClientHttpRequestFactoryの使用を推奨とする。 |
(4)
|
リクエスト送信前処理
|
HttpMessageConverterの実装クラスを使用して、Javaオブジェクトをリクエストボディに設定する電文(JSON等)に変換する。なお、実際に利用される
HttpMessageConverterの実装クラスは、Javaオブジェクトの型とメディアタイプをもとにRestTemplateが決定する。 |
(5)
|
リクエスト送信
|
ClientHttpRequestInterceptorを設定している場合、ClientHttpRequestInterceptorの実装クラスを使用してリクエスト送信前後の共通処理を行う。ClientHttpRequestに電文(JSON等)をリクエストボディに設定して、REST API(Web API)にHTTP経由でリクエストを送信する。REST API(Web API)からレスポンスを受信後、
ClientHttpRequestがClientHttpResponseを生成し、受信したレスポンスデータを設定する。 |
(6)
|
エラーハンドリング
|
ResponseErrorHandlerの実装クラスにて、ClientHttpResponseに設定されているHTTPステータスコードをもとに、エラー判定及びエラー処理を行う。なお、
ResponseErrorHandlerの実装クラスを指定していない場合は、デフォルト実装クラスであるDefaultResponseErrorHandlerが使用される。 |
(7)
|
レスポンス取得後処理
|
レスポンス取得後の後処理として、
ResponseExtractorの実装クラスが実行される。なお、
ResponseExtractorの実装クラスを指定していない場合は、デフォルト実装クラスであるHttpMessageConverterExtractorが使用される。後処理時に、
HttpMessageConverterの実装クラスを使用して、レスポンスボディに設定されている電文(JSON等)をJavaオブジェクトに変換する。リクエスト送信前処理と同様で
HttpMessageConverterは、Javaオブジェクトの型とメディアタイプをもとに決定される。 |
(8)
|
アプリケーションへ実行結果返却
|
REST API(Web API)の呼び出し結果(Javaオブジェクト)をアプリケーションへ返却する。
|
5.2.1.2. RestTemplateの構成要素と利用上のポイント¶
RestTemplateで利用される構成要素の概要を示し、利用上、有益と思われるいくつかの内容について記載する。5.2.1.2.1. URLの生成(UriBuilderFactory)¶
org.springframework.web.util.UriBuilderFactoryは、URIテンプレート(プレースホルダ付きのURLの元となる文字列)と、URIテンプレート変数の値(プレースホルダを埋めるためのパラメータ)を受け取り、リクエスト送信先のURLを生成するインターフェースである。
具体的には以下のようなURL文字列を生成する。
URIテンプレート:
http://example.com/api/users/{userId}URIテンプレート変数の値:
12345生成されるURL:
http://example.com/api/users/12345
UriBuilderFactoryは、UriBuilderを生成するためのファクトリであり、実際のURL文字列の生成はUriBuilderで行われる。org.springframework.web.util.DefaultUriBuilderFactoryから、DefaultUriBuilderが作られ、URL生成に利用される。UriBuilderFactoryを拡張することが考えられる。UriBuilderの実装クラスと、そのUriBuilderの実装クラスが使われるようにしたUriBuilderFactoryの実装クラスを作成し、このUriBuilderFactoryの実装クラスが使われるように、Bean定義を行うことで実現できる。UriBuilderFactoryの具体的なカスタマイズの例は、「URL生成処理をカスタマイズする方法(UriBuilderFactory)」を参照されたい。5.2.1.2.2. リクエストの生成(ClientHttpRequestFactory)¶
RestTemplateは、サーバとの通信処理を以下の3つのインタフェースの実装クラスに委譲することで実現している。org.springframework.http.client.ClientHttpRequestFactoryorg.springframework.http.client.ClientHttpRequestorg.springframework.http.client.ClientHttpResponse
ClientHttpRequestFactoryは、サーバとの通信処理を行うClientHttpRequestインタフェースの実装クラスの生成を行う。ClientHttpRequestがサーバと通信を行い、通信結果を保持するClientHttpResponseインタフェースの実装クラスを生成し、通信結果を設定する。ClientHttpRequestFactoryについては、Spring Framework側でいくつかの実装が用意されており、細かな点で動作が異なることがあるため、利用者側で何を利用するか意識しておく必要がある。ClientHttpRequestFactoryの実装クラスは以下の通りである。項番 |
クラス名 |
説明 |
|---|---|---|
(1)
|
org.springframework.http.client.HttpComponentsClientHttpRequestFactory |
Apache HttpComponents HttpClientのAPIを使用して同期型の通信処理を行うための実装クラス。(HttpClient 5.2以上が必要)
|
(2)
|
org.springframework.http.client.JettyClientHttpRequestFactory |
JettyのAPIを使用して同期型の通信処理を行うための実装クラス。
|
(3)
|
org.springframework.http.client.ReactorClientHttpRequestFactory |
Reactor NettyのAPIを使用して同期型の通信処理を行うための実装クラス。
|
(4)
|
org.springframework.http.client.JdkClientHttpRequestFactory |
Java SE標準のHttpClientのAPIを使用して同期型の通信処理を行うための実装クラス。
HttpClientの実装としては、JDK 11にて追加された
java.net.http.HttpClientを利用しており、SimpleClientHttpRequestFactoryよりも高機能な実装クラスである。 |
(5)
|
org.springframework.http.client.SimpleClientHttpRequestFactory |
Java SE標準のHttpURLConnectionのAPIを使用して同期型の通信処理を行うための実装クラス。
HttpClientの実装としては、JDK初期から存在する
java.net.HttpURLConnectionを使用しており、単純な通信設定のみが行える。なお、
RestTemplate利用時にデフォルトで設定される実装クラスである。 |
使用するClientHttpRequestFactoryの実装クラスについて
RestTemplateで使用されるClientHttpRequestFactoryのデフォルト実装クラスには、SimpleClientHttpRequestFactoryが設定されている。SimpleClientHttpRequestFactoryは、コネクションプールやプロキシ認証等の機能を持たず、他のClientHttpRequestFactoryの実装と比較すると機能が限定されている。RestTemplateにClientHttpRequestFactoryの実装を指定しない場合、SimpleClientHttpRequestFactoryが自動設定されるため、推奨設定を適用するにはHttpComponentsClientHttpRequestFactoryをRestTemplateに明示的に指定する必要がある。ApplicationContextConfig.java
@Bean("restTemplate")
public RestTemplate restTemplate() {
return new RestTemplate(new HttpComponentsClientHttpRequestFactory());
}
applicationContext.xml
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" />
</constructor-arg>
</bean>
ClientHttpRequestFactoryの設定について
ClientHttpRequestFactoryでは、通信設定のカスタマイズが可能となっており、以下のような設定が可能である。タイムアウトの設定:「通信タイムアウトの設定」
SSLの設定:「カスタマイズしたキーストアファイルの利用(SSL/TLS)」
プロキシの設定:「HTTP Proxyサーバの設定方法」
Note
Content-Lengthヘッダについて
Spring Framework 6.1よりメモリ使用量を減らすために、ほとんどのClientHttpRequestFactory実装クラスはサーバへ送信する前にリクエストボディをバッファリングしなくなった。これにより、Content-Lengthヘッダが設定されなくなったため、Content-Lengthヘッダを設定する必要がある場合はBufferingClientHttpRequestFactoryを利用しバッファリングする必要がある。
Bean定義ファイルの定義例
ApplicationContextConfig.java
@Bean("restTemplate") public RestTemplate restTemplate() { return new RestTemplate(new BufferingClientHttpRequestFactory(new HttpComponentsClientHttpRequestFactory())); // (1) }
項番
説明
(1)BufferingClientHttpRequestFactoryのコンストラクタにHttpComponentsClientHttpRequestFactoryを設定しインスタンスを生成する。生成したBufferingClientHttpRequestFactoryをRestTemplateに設定することで、バッファリングが有効になる。applicationContext.xml
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <constructor-arg> <bean class="org.springframework.http.client.BufferingClientHttpRequestFactory"> <!-- (1) --> <constructor-arg> <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" /> </constructor-arg> </bean> </constructor-arg> </bean>
項番
説明
(1)BufferingClientHttpRequestFactoryのコンストラクタにHttpComponentsClientHttpRequestFactoryを設定しインスタンスを生成する。生成したBufferingClientHttpRequestFactoryをRestTemplateに設定することで、バッファリングが有効になる。
5.2.1.2.3. リクエスト初期化(ClientHttpRequestInitializer)¶
org.springframework.http.client.ClientHttpRequestInitializerは、Httpリクエスト送信前にClientHttpRequestをカスタマイズするためのインタフェースである。ClientHttpRequestInitializerの他に、 ClientHttpRequestInterceptorも存在するが、それぞれ用途が異なる。両者の違いについては「ClientHttpRequestInterceptorとClientHttpRequestInitializerの使い分けについて」を参照されたい。ClientHttpRequestInitializerはユーザがRestTemplateに設定した場合のみ動作し、ClientHttpRequestFactoryによるClientHttpRequestの生成後、ClientHttpRequestInitializerに実装したユーザ定義の初期化処理が実行される。ClientHttpRequestInitializerはデフォルト実装はなく、拡張ポイントとして提供されているインターフェースである。ClientHttpRequestInitializerの利用が適しているケースには、リクエストヘッダにカスタムヘッダを一括で設定したい場合などがある。5.2.1.2.4. メッセージ変換(HttpMessageConverter)¶
org.springframework.http.converter.HttpMessageConverterは、アプリケーションで扱うJavaオブジェクトとサーバと通信するための電文(JSON等)を相互に変換するためのインタフェースである。
HttpMessageConverter実装を提供しており、RestTemplateが使用するHttpMessageConverterは、RestTemplateの生成時に登録され、リクエスト/レスポンスのボディ変換に利用される。HttpMessageConverterには、標準で登録されるHttpMessageConverterと、依存ライブラリの有無により登録されるHttpMessageConverterが存在する。HttpMessageConverterを必要に応じて追加登録することも可能である。HttpMessageConverterはクライアント側とサーバ側のそれぞれで登録されるため、本節で説明しているHttpMessageConverterはクライアント側で利用されるものであることに留意されたい。HttpMessageConverterは、標準の構成では以下の順で登録される。独自で変換処理を実装した
HttpMessageConverter標準で登録される
HttpMessageConverter依存ライブラリの有無により登録される
HttpMessageConverter
HttpMessageConverterの順序に従って、Javaオブジェクトの型とメディアタイプに適用可能なHttpMessageConverterが選択される。HttpMessageConverterが対応するJavaオブジェクトの型とメディアタイプについては、以下のJavaDocを参照されたい。標準で登録されるHttpMessageConverter
依存ライブラリの有無により登録されるHttpMessageConverter
上記以外のHttpMessageConverterに関しては、HttpMessageConverterの実装クラスの一覧を参照されたい。
ガイドラインで使用するHttpMessageConverterについて
HttpMessageConverterを同等の構成で登録していることを前提とする。org.springframework.http.converter.json.MappingJackson2HttpMessageConverterによる変換処理を例に説明を行う。RestTemplateの送受信で使用するJavaBeanのフィールド名は、JSONのプロパティ名と一致していることを前提とし、特別なマッピング等は行っていない。5.2.1.2.5. リクエスト前後処理(ClientHttpRequestInterceptor)¶
org.springframework.http.client.ClientHttpRequestInterceptorは、サーバとの通信の前後に共通的な処理を実装するためのインタフェースである。ClientHttpRequestInitializerもあるため、違いについては「ClientHttpRequestInterceptorとClientHttpRequestInitializerの使い分けについて」を参照されたい。ClientHttpRequestInterceptorを使用すると、サーバとの通信時のリクエストやレスポンスのログを出力するといった共通的な処理をRestTemplateに適用することができる。ClientHttpRequestInterceptorを使用した共通処理の実装方法については、「リクエスト送信前後の共通処理の適用(ClientHttpRequestInterceptor)」を参照されたい。ClientHttpRequestInterceptorの動作仕様
ClientHttpRequestInterceptorは複数適用することができ、RestTemplateに登録した順番でチェーン実行される。ClientHttpRequestによるHTTP通信処理が登録されている。ClientHttpRequestInterceptorを複数適用した場合の実装例は、「複数のClientHttpRequestInterceptorを適用する方法」を参照されたい。Warning
ClientHttpRequestInterceptor使用時の注意点
ClientHttpRequestInterceptorを利用すると、リクエストボディが引数として渡される関係でボディ部がメモリ上に全て展開されてしまう。したがって、リクエストボディに大量のデータを含む場合はメモリ使用量が増大する可能性があるので留意して使用する必要がある。
5.2.1.2.6. ClientHttpRequestInterceptorとClientHttpRequestInitializerの使い分けについて¶
ClientHttpRequestInterceptorとClientHttpRequestInitializerは、どちらもリクエスト送信時の共通処理を記述するためのインタフェースであるが、以下のように目的と使用方法が異なるため、使い分けが重要である。項番 |
種類 |
説明 |
|---|---|---|
(1)
|
ClientHttpRequestInitializer |
ClientHttpRequestFactoryがClientHttpRequestを生成した後に実行される。リクエストボディを読み込まず、
ClientHttpRequestのみに対してカスタマイズを行える拡張ポイントであるため、リクエストヘッダに対して特定のヘッダを追加する等のClientHttpRequestに限定された共通処理を記述するのに適している。 |
(2)
|
ClientHttpRequestInterceptor |
実行タイミングとしては、
ClientHttpRequestがサーバとの通信を行う前後に実行される。したがって、リクエスト前後の共通処理であるログ出力等を追加したい場合に利用するのに適している。
|
ClientHttpRequestInterceptorを利用するとリクエストボディがメモリ上に展開されるため、メモリ使用量が増大し、パフォーマンスに影響を与える可能性がある。ClientHttpRequestInitializerを利用する方が適切である。5.2.1.2.7. エラーハンドリング(ResponseErrorHandler)¶
RestTemplateによるサーバ通信時のエラーに対応するためのエラーハンドラとしてResponseErrorHandlerを提供している。RestTemplateでは、ResponseErrorHandlerのデフォルト実装としてDefaultResponseErrorHandlerが設定されており、デフォルトでHTTPステータスコードに応じたエラーハンドリングが実装されている。項番 |
HTTPステータスコード |
動作仕様 |
|---|---|---|
(1)
|
2xx(正常系)
|
エラー処理は行わない。
|
(2)
|
4xx(クライアントエラー系)
|
org.springframework.web.client.HttpClientErrorExceptionを発生させる。 |
(3)
|
5xx(サーバエラー系)
|
org.springframework.web.client.HttpServerErrorExceptionを発生させる。 |
ResponseErrorHandlerの実装を作成して、RestTemplateに設定する必要がある。ResponseErrorHandlerのカスタマイズ方法と設定については、「エラーハンドリング」を参照されたい。5.2.1.2.8. レスポンス取得後処理(ResponseExtractor)¶
RestTemplateでサーバからのレスポンスを取得したあとの、ボディの抽出や変換などの処理(レスポンス取得後処理)を実現する仕組みとして、 ResponseExtractorを提供している。RestTemplateで利用されるResponseExtractorの動作仕様は以下の通り。
ResponseExtractorの動作
org.springframework.web.client.ResponseExtractorを使用して後処理を実装する。デフォルトで利用される実装が提供されており、org.springframework.web.client.HttpMessageConverterExtractorが利用される。動作としては、RestTemplateがレスポンスを受信し、HTTPステータスのエラー判定処理を行った後に実行され、後処理としてメッセージの変換処理を行う。
受信したレスポンスを操作する必要がある場合などは ResponseExtractorをカスタマイズする。実際の方法は、「ファイルダウンロード」で説明しているので、必要に応じてこちらも参照されたい。
5.2.2. How to use(同期通信)¶
RestTemplateを使用した、同期通信を行うRESTクライアントの実装方法について説明する。RestTemplateは他のHTTPメソッド(PUT, PATCH, DELETE, HEAD, OPTIONSなど)もサポートしており、同様に実装することができる。5.2.2.1. 同期通信のセットアップ¶
同期通信を行う場合は、RestTemplateをDIコンテナに登録し、RESTクライアントを利用するコンポーネントにインジェクションする。
5.2.2.1.1. 依存ライブラリ設定¶
RestTemplateを使用するためにpom.xmlに、Spring Frameworkのspring-webライブラリと、HttpClientの実装としてApache HttpComponents HttpClientライブラリを追加する。pom.xmlに追加する。<dependencies>
<!-- (1) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<!-- (2) -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
</dependencies>
項番 |
説明 |
|---|---|
(1)
|
Spring Frameworkの
spring-webライブラリをdependenciesに追加する。 |
(2)
|
Apache HttpComponents HttpClient を依存ライブラリに追加する。
本設定は、
ClientHttpRequestFactoryのインターフェース実装として、HttpComponentsClientHttpRequestFactoryを利用するための設定である。 |
5.2.2.1.2. RESTクライアントのBean定義¶
RestTemplateのBean定義を行い、DIコンテナに登録する。これらのBeanは、クライアントアプリケーションにおける、リクエスト送信の起点となる。RestTemplateのBean定義時にはClientHttpRequestFactoryを通して実際のリクエスト作成で利用する実装を選択することができる。ClientHttpRequestFactory実装であるHttpComponentsClientHttpRequestFactoryの利用を前提とする。HttpComponentsClientHttpRequestFactoryは、Spring Framework 6.1よりサーバへのリクエスト送信を行う前にリクエストボディをバッファリングしないようになったため、リクエストのContent-Lengthヘッダが設定されなくなった。Bean定義ファイルの定義例
ApplicationContextConfig.java
@Bean("restTemplate")
public RestTemplate restTemplate() {
return new RestTemplate(new HttpComponentsClientHttpRequestFactory()); // (1)
}
項番 |
説明 |
|---|---|
(1)
|
RestTemplateのコンストラクタの引数に、org.springframework.http.client.HttpComponentsClientHttpRequestFactoryを設定し、Beanを生成、DIコンテナへ登録する。HttpComponentsClientHttpRequestFactoryを利用した通信設定については「リクエスト生成処理のカスタマイズ方法(ClientHttpRequestFactory)」を参照されたい。 |
applicationContext.xml
<!-- (1) -->
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" />
</constructor-arg>
</bean>
項番 |
説明 |
|---|---|
(1)
|
RestTemplateのコンストラクタの引数に、org.springframework.http.client.HttpComponentsClientHttpRequestFactoryを設定し、Beanを生成、DIコンテナへ登録する。HttpComponentsClientHttpRequestFactoryを利用した通信設定については「リクエスト生成処理のカスタマイズ方法(ClientHttpRequestFactory)」を参照されたい。 |
代表的なRestTemplateのBeanのカスタマイズ方法は、下記で実装例を示しているため、必要に応じて参照されたい。
5.2.2.1.3. RESTクライアントの利用¶
RESTクライアントを利用する場合は、DIコンテナに登録されているRestTemplateをインジェクションする。
RESTクライアントのインジェクション例
@Service
public class AccountServiceImpl implements AccountService {
@Inject
RestTemplate restTemplate;
// omitted
}
5.2.2.2. 基準となるURLを設定する¶
baseUrlを利用すると、REST APIにアクセスする際の基準となるURLをあらかじめ設定しておくことができる。http://localhost:8080/api/v1」をbaseUrlとして設定しておくことで、クライアント側の実装では後続のパス「/users」のみを指定することで、完全なURLが自動的に生成される。baseUrlを指定していても、RESTクライアントを使用する際に指定するURIに絶対URLを明示的に指定した場合は、baseUrlの設定は無効となる。URLの例
URL:
http://localhost:8080/api/v1/usersbaseUrl:
http://localhost:8080/api/v1クライアントで指定するパス:
/users
baseUrlの設定は、DefaultUriBuilderFactoryにbaseUrlを設定して、RestTemplateに適用する必要がある。
ApplicationContextConfig.java
@Value("${api.baseUrl:http://localhost:8080/api/v1}") // (1)
private String url;
@Bean("restTemplate")
public RestTemplate restTemplate() {
var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(url)); // (2)
return restTemplate;
}
項番 |
説明 |
|---|---|
(1)
|
プロパティファイルから基準となるURLを取得し設定を行う。
|
(2)
|
DefaultUriBuilderFactoryのコンストラクタに(1)のURLを設定してインスタンスを生成する。RestTemplateのsetUriTemplateHandlerメソッドを使用して、生成したDefaultUriBuilderFactoryのインスタンスを設定する。 |
applicationContext.xml
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate" >
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" />
</constructor-arg>
<property name="uriTemplateHandler"> <!-- (2) -->
<bean class="org.springframework.web.util.DefaultUriBuilderFactory">
<constructor-arg type="java.lang.String" value="${api.baseUrl:http://localhost:8080/api/v1}" /> <!-- (1) -->
</bean>
</property>
</bean>
項番 |
説明 |
|---|---|
(1)
|
プロパティファイルから基準となるURLを取得する。
DefaultUriBuilderFactoryのコンストラクタに取得したURLを指定してBeanを生成する。 |
(2)
|
RestTemplateのuriTemplateHandlerプロパティを使用して、生成したDefaultUriBuilderFactoryのBeanを設定する。 |
利用時のURLは以下の通りとなる。
private String uri = "/users/{id}"; // (1)
@Inject
private RestTemplate restTemplate;
public User getUser(String id) {
return restTemplate.getForObject(uri, User.class, id);
}
項番 |
説明 |
|---|---|
(1)
|
baseUrlで指定したURL以降のパスのみを指定する。 |
baseUrlを設定しない、または絶対URLを指定してbaseUrlの設定を上書きする場合は、以下の実装を行う。
@Value("${api.baseUrl:http://localhost:8080/api/v2}/users/{id}") // (1)
private String uri;
@Inject
private RestTemplate restTemplate;
public User getUser(String id) {
return restTemplate.getForObject(uri, User.class, id);
}
項番 |
説明 |
|---|---|
(1)
|
プロパティファイルから絶対パスで記載されたURLを取得し、利用するURLとして指定する。
|
5.2.2.3. データの取得(GETリクエスト送信)¶
RestTemplateは、GETリクエスト送信を行うためのメソッドを複数提供している。代表的なメソッドを以下に示す。項番 |
用途 |
RestTemplateのメソッド |
|---|---|---|
(1)
|
レスポンスボディを任意のデータ型で取得する
|
RestTemplate.getForObject(...) |
(2)
|
レスポンス情報を含む結果を取得する
|
RestTemplate.getForEntity(...) |
5.2.2.3.1. GETメソッドを指定したリクエスト送信の実装¶
Userを表現するJavaBeanクラスにてレスポンスボディを受け取る実装である。getForObjectメソッドの使用例
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
private URI uri;
メソッド内部
User user = restTemplate.getForObject(uri, User.class); // (1)
項番 |
説明 |
|---|---|
(1)
|
getForObjectメソッドを使用した場合は、戻り値はレスポンスボディの値になる。レスポンスボディのデータは
HttpMessageConverterによって第2引数に指定したJavaクラスへ変換された後、返却される。 |
5.2.2.3.2. ステータスコードやレスポンスヘッダを含めたレスポンスの取得¶
getForEntityメソッドの使用例
インポート宣言
import org.springframework.http.ResponseEntity;
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
private URI uri;
メソッド内部
ResponseEntity<User> responseEntity =
restTemplate.getForEntity(uri, User.class); // (1)
HttpStatusCode statusCode = responseEntity.getStatusCode(); // (2)
HttpHeaders header = responseEntity.getHeaders(); // (3)
User user = responseEntity.getBody(); // (4)
項番 |
説明 |
|---|---|
(1)
|
getForEntityメソッドを使用した場合は、戻り値はorg.springframework.http.ResponseEntityとなる。 |
(2)
|
HTTPステータスコードは
ResponseEntityのgetStatusCodeメソッドを用いて取得する。 |
(3)
|
レスポンスヘッダは
ResponseEntityのgetHeadersメソッドを用いて取得する。 |
(4)
|
レスポンスボディは
ResponseEntityのgetBodyメソッドを用いて取得する。 |
Note
ResponseEntityとは
ResponseEntityはHTTPレスポンスを表すクラスで、HTTPステータスコード、レスポンスヘッダ、レスポンスボディの情報を取得することができる。
詳細はResponseEntityのJavadocを参照されたい。
5.2.2.3.3. コレクション形式のデータ取得¶
サーバから応答されるレスポンスボディの電文(JSON等)がコレクション形式の場合は、以下の実装を行う。
コレクション形式のデータの取得例
getForObjectメソッドでは、コレクション形式のデータをレスポンスの型として指定できないため、exchangeメソッドを使用してリクエストを送信する必要がある。exchangeメソッドの使い方については、「リクエスト送信単位でリクエストヘッダを設定する方法」にて説明を行っているのでこちらを参照されたい。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<レスポンスデータの型>を指定する。 |
(3)
|
getBodyメソッドで、レスポンスボディのデータを取得する。 |
5.2.2.4. データの登録(POSTリクエスト送信)¶
RestTemplateは、POSTリクエスト送信を行うためのメソッドを複数提供している。代表的なメソッドの一覧を以下に示す。項番 |
用途 |
RestTemplateのメソッド |
|---|---|---|
(1)
|
レスポンスボディを任意のデータ型で取得する
|
RestTemplate.postForObject(...) |
(2)
|
レスポンス情報を含む結果を取得する
|
RestTemplate.postForEntity(...) |
5.2.2.4.1. POSTメソッドを指定したリクエスト送信の実装¶
Userを表現するJavaBeanクラスにてレスポンスボディを受け取る実装である。postForObjectメソッドの使用例
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
private URI uri;
メソッド内部
var user = new User();
// omitted
User user = restTemplate.postForObject(uri, user, User.class); // (1)
項番 |
説明 |
|---|---|
(1)
|
postForObjectメソッドは、簡易にPOSTリクエストを実装できる。第二引数には、
HttpMessageConverterによってリクエストボディに変換されるJavaオブジェクトを設定する。postForObjectメソッドを使用した場合は、戻り値はレスポンスボディの値になる。 |
5.2.2.5. エラーハンドリング¶
5.2.2.5.1. 例外ハンドリング(デフォルトの動作)¶
例外ハンドリングの実装例
フィールド宣言部
@Value("${api.retry.maxCount}")
int retryMaxCount;
@Value("${api.retry.retryWaitTimeCoefficient}")
int retryWaitTimeCoefficient;
メソッド内部
int retryCount = 0;
while (true) {
try {
User user = restTemplate.getForObject(uri, User.class);
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); // (2)
}
try {
Thread.sleep(retryWaitTimeCoefficient * retryCount);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
// omitted
}
}
項番 |
説明 |
|---|---|
(1)
|
例外をキャッチしてエラー処理を行う。
上記の例では、サーバエラー(500系)時に
throwされるHttpServerErrorExceptionをキャッチしてリトライ処理を行っている。 |
5.2.2.5.2. HTTPステータスコードでハンドリング(エラーハンドラの拡張)¶
RestTemplateでは、ResponseErrorHandlerインタフェースの実装クラスをRestTemplateに設定することで、独自のエラー処理を行うことができる。ResponseEntityを返却し、HTTPステータスコードでエラーハンドリングが行えるように拡張をしている。エラーハンドラの実装クラスの作成例
import java.io.IOException;
import java.net.URI;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
public class CustomErrorHandler extends DefaultResponseErrorHandler { // (1)
// (2)
@Override
public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
// Don't throw Exception.
}
}
項番 |
説明 |
|---|---|
(1)
|
ResponseErrorHandlerインタフェースの実装クラスを作成する。上記の例では、エラーハンドラのデフォルト実装クラスである
DefaultResponseErrorHandlerを拡張している。 |
(2)
|
handleErrorメソッドには、拡張元であるDefaultResponseErrorHandlerのhasErrorメソッドでエラーと判断されたHTTPステータスコードに対するエラー処理を実装する。DefaultResponseErrorHandlerのhasErrorメソッドがエラーとみなすHTTPステータスコードは、サーバエラー(5xx系)及びクライアントエラー(4xx系)が対象である。詳細は「DefaultResponseErrorHandlerのJavadoc」を参照されたい。
上記の例では、サーバエラー(5xx系)及びクライアントエラー(4xx系)が発生した場合でも例外を発生させないことで、
RestTemplateの呼び出し側でResponseEntityを受け取れるようにしている。 |
Bean定義ファイルの定義例
ApplicationContextConfig.java
// (1)
@Bean("customErrorHandler")
public CustomErrorHandler customErrorHandler() {
return new CustomErrorHandler();
}
@Bean("restTemplate")
public RestTemplate restTemplate() {
var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
restTemplate.setErrorHandler(customErrorHandler()); // (2)
return restTemplate;
}
項番 |
説明 |
|---|---|
(1)
|
ResponseErrorHandlerの実装クラスのBean定義を行う。 |
(2)
|
RestTemplateのsetErrorHandlerメソッドに、(1)で生成したResponseErrorHandlerの実装クラスのBeanを設定する。 |
Note
Spring Frameworkが提供するNoOpResponseErrorHandlerについて
サーバエラー及びクライアントエラーが発生した場合でも例外を発生させずにResponseEntityを返却する実装であれば、同様の動作を行うNoOpResponseErrorHandlerが ResponseErrorHandlerの実装クラスとしてSpring Frameworkから提供されているため、こちらで代替することも可能である。
ApplicationContextConfig.java// (1) @Bean("noOpResponseErrorHandler") public NoOpResponseErrorHandler noOpResponseErrorHandler() { return new NoOpResponseErrorHandler(); } @Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setErrorHandler(noOpResponseErrorHandler()); // (2) return restTemplate; }
項番
説明
(1)org.springframework.web.client.NoOpResponseErrorHandlerのBean定義を行う。(2)RestTemplateのsetErrorHandlerメソッドにNoOpResponseErrorHandlerのBeanを設定する。
applicationContext.xml
<bean id="customErrorHandler" class="com.example.restclient.CustomErrorHandler" /> <!-- (1) -->
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" />
</constructor-arg>
<property name="errorHandler" ref="customErrorHandler" /><!-- (2) -->
</bean>
項番 |
説明 |
|---|---|
(1)
|
ResponseErrorHandlerの実装クラスのBean定義を行う。 |
(2)
|
errorHandlerプロパティに、(1)で生成したResponseErrorHandlerの実装クラスのBeanをインジェクションする。 |
Note
Spring Frameworkが提供するNoOpResponseErrorHandlerについて
サーバエラー及びクライアントエラーが発生した場合でも例外を発生させずにResponseEntityを返却する実装であれば、同様の動作を行うNoOpResponseErrorHandlerが ResponseErrorHandlerの実装クラスとしてSpring Frameworkから提供されているため、こちらで代替することも可能である。
applicationContext.xml<bean id="noOpResponseErrorHandler" class="org.springframework.web.client.NoOpResponseErrorHandler" /> <!-- (1) --> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <constructor-arg> <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" /> </constructor-arg> <property name="errorHandler" ref="noOpResponseErrorHandler" /><!-- (2) --> </bean>
項番
説明
(1)org.springframework.web.client.NoOpResponseErrorHandlerのBean定義を行う。(2)errorHandlerプロパティにNoOpResponseErrorHandlerの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();
}
// omitted
}
}
項番 |
説明 |
|---|---|
(1)
|
上記の実装例では、エラー時にも
ResponseEntityを返すようにエラーハンドラを拡張しているので、返却されたResponseEntityからHTTPステータスコードを取得して、処理結果が正常であったか確認する必要がある。 |
(2)
|
エラー発生時も返却された
ResponseEntityからHTTPステータスコードを取得して、その値に応じて処理を制御することができる。 |
5.2.2.6. リクエストヘッダの設定¶
5.2.2.6.1. アプリケーション全体で共通のリクエストヘッダを設定する方法¶
RestTemplateではClientHttpRequestInitializerを使用することで共通のリクエストヘッダの設定が可能である。ClientHttpRequestInitializerの実装例
// (1)
public class CustomHeaderInitializer implements ClientHttpRequestInitializer {
// (2)
@Override
public void initialize(ClientHttpRequest request) {
request.getHeaders().add("Custom-Header", "CustomHeaderValue"); // (3)
}
}
項番 |
説明 |
|---|---|
(1)
|
ClientHttpRequestInitializerの実装クラスを作成する。 |
(2)
|
initializeメソッドにClientHttpRequestの初期化処理を実装する。上記の例では、リクエストヘッダにカスタムヘッダを付与している。
|
(3)
|
リクエストヘッダの名前と値を設定する。
上記の例では、
Custom-HeaderというキーでCustomHeaderValueという値を設定している。 |
Bean定義ファイルの定義例
ApplicationContextConfig.java
// (1)
@Bean("customHeaderInitializer")
public CustomHeaderInitializer customHeaderInitializer() {
return new CustomHeaderInitializer();
}
@Bean("restTemplate")
public RestTemplate restTemplate() {
var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
restTemplate.setClientHttpRequestInitializers(List.of(customHeaderInitializer())); // (2)
return restTemplate;
}
項番 |
説明 |
|---|---|
(1)
|
ClientHttpRequestInitializerのBean定義を行う。 |
(2)
|
RestTemplateのsetClientHttpRequestInitializersメソッドにClientHttpRequestInitializerのBeanを設定する。setClientHttpRequestInitializersメソッドは、List型で引数が定義されているため、複数のClientHttpRequestInitializerを設定することができる。 |
applicationContext.xml
<!-- (1) -->
<bean id="customHeaderInitializer" class="com.example.restclient.CustomHeaderInitializer" />
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" />
</constructor-arg>
<property name="clientHttpRequestInitializers">
<list>
<ref bean="customHeaderInitializer" />
</list>
</property>
</bean>
項番 |
説明 |
|---|---|
(1)
|
ClientHttpRequestInitializerのBean定義を行う。 |
(2)
|
RestTemplateのclientHttpRequestInitializersプロパティにClientHttpRequestInitializerのBeanを設定する。clientHttpRequestInitializersプロパティは、List型で定義されているため、複数のClientHttpRequestInitializerを設定することができる。 |
5.2.2.6.2. リクエスト送信単位でリクエストヘッダを設定する方法¶
RequestEntityのメソッドを使用してHTTPヘッダを設定する。Content-TypeやAcceptといった代表的なHTTPヘッダについては、RequestEntityが提供するBuilderに専用の設定メソッド(contentType、accept)が用意されているため、これを利用すると良い。RequestEntity.HeadersBuilderのheaderメソッドを使用して設定する。5.2.2.6.2.1. Content-Typeヘッダの設定¶
Content-Typeヘッダの設定例
インポート宣言
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
private URI uri;
メソッド内部
var user = new User();
// omitted
RequestEntity<User> requestEntity = RequestEntity
.post(uri) // (1)
.contentType(MediaType.APPLICATION_JSON) // (2)
.body(user); // (3)
ResponseEntity<User> responseEntity =
restTemplate.exchange(requestEntity, User.class); //(4)
User user = responseEntity.getBody();
項番 |
説明 |
|---|---|
(1)
|
RequestEntityのpostメソッドを使用し、POSTリクエスト用のリクエストビルダを生成する。パラメータにURIを設定する。
必要に応じて、リクエストビルダのメソッドを使用し、リクエストヘッダやリクエストボディを設定する。
|
(2)
|
RequestEntity.BodyBuilderのcontentTypeメソッドを使用し、Content-Typeヘッダの値を指定する。上記の実装例では、送信時のデータ形式がJSONであることを示す「
application/json」を設定している。 |
(3)
|
RequestEntity.BodyBuilderのbodyメソッドにて、リクエストボディを設定し、RequestEntityオブジェクトを作成する。ボディの設定が不要の場合は、
buildメソッドを使用してRequestEntityオブジェクトを作成する。 |
(4)
|
exchangeメソッドを使用し、リクエストを送信する。引数には、(3)で生成した
RequestEntityと、レスポンスデータの型を指定する。レスポンスは
ResponseEntity<T>で返却され、型パラメータは引数で指定したレスポンスデータの型となる。 |
Note
RequestEntityとは
RequestEntityはHTTPリクエストを表すクラスで、接続URI、HTTPメソッド、リクエストヘッダ、リクエストボディを設定することができる。
詳細はRequestEntityのJavadocを参照されたい。
5.2.2.6.2.2. Acceptヘッダの設定¶
Acceptヘッダの設定例
メソッド内部
RequestEntity<Void> requestEntity = RequestEntity
.get(uri)
.accept(MediaType.APPLICATION_JSON) // (1)
.build(); // (2)
ResponseEntity<User> responseEntity =
restTemplate.exchange(requestEntity, User.class);
User user = responseEntity.getBody();
項番 |
説明 |
|---|---|
(1)
|
RequestEntity.HeadersBuilderのacceptメソッドを使用して、Acceptヘッダの値を設定する。上記の実装例では、取得可能なデータ形式がJSONであることを示す「
application/json」を設定している。 |
(2)
|
RequestEntity.HeadersBuilderのbuildメソッドを使用し、RequestEntityオブジェクトを作成する。 |
5.2.2.6.2.3. 任意のリクエストヘッダの設定¶
カスタムヘッダの実装例
メソッド内部
RequestEntity<Void> requestEntity = RequestEntity
.get(uri)
.header("Custom-Header", "CustomValue") // (1)
.build();
ResponseEntity<User> responseEntity =
restTemplate.exchange(requestEntity, User.class);
User user = responseEntity.getBody();
項番 |
説明 |
|---|---|
(1)
|
RequestEntity.HeadersBuilderのheaderメソッドを使用してリクエストヘッダの名前と値を設定する。 |
5.2.2.8. 通信タイムアウトの設定¶
ClientHttpRequestFactoryの実装クラスを定義し、実装クラスのプロパティ値を設定することで実現することができる。ClientHttpRequestFactoryの実装の定義と、タイムアウトの設定方法について説明を行う。5.2.2.8.1. HttpComponentsClientHttpRequestFactoryを使用した通信タイムアウトの設定¶
ClientHttpRequestFactoryの実装クラスのプロパティ値を設定することで実現することができる。HttpComponentsClientHttpRequestFactoryを使用した定義例である。Bean定義ファイルの定義例
ApplicationContextConfig.java
// (1)
@Value("${api.connectTimeout:2000}")
private int connectTimeout;
// (2)
@Value("${api.readTimeout:2000}")
private int readTimeout;
// omitted
@Bean("clientHttpRequestFactory")
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
// (3)
var connectionConfig = ConnectionConfig.custom()
.setConnectTimeout(Timeout.ofMilliseconds(connectTimeout))
.build();
// (4)
var poolManager = PoolingHttpClientConnectionManagerBuilder.create()
.setDefaultConnectionConfig(connectionConfig)
.build();
// (5)
var httpClient = HttpClientBuilder.create()
.setConnectionManager(poolManager)
.build();
// (6)
var bean = new HttpComponentsClientHttpRequestFactory(httpClient);
bean.setReadTimeout(readTimeout); // (7)
return bean;
}
@Bean("timeoutRestTemplate")
public RestTemplate timeoutRestTemplate() {
return new RestTemplate(clientHttpRequestFactory()); // (8)
}
項番 |
説明 |
|---|---|
(1)
|
サーバとの接続タイムアウトの時間(ミリ秒)を設定する。
|
(2)
|
レスポンスデータの読み込みタイムアウトの時間(ミリ秒)を設定する。
|
(3)
|
org.apache.hc.client5.http.config.ConnectionConfig.customメソッドにてBuilderを生成する。生成した
BuilderのsetConnectTimeoutメソッドに、(1)の接続タイムアウト時間を設定する。buildメソッドにてConnectionConfigのインスタンスを生成する。 |
(4)
|
org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder.createメソッドにてPoolingHttpClientConnectionManagerBuilderを生成する。生成した
PoolingHttpClientConnectionManagerBuilderのsetDefaultConnectionConfigメソッドに、(3)で生成したConnectionConfigのインスタンスを設定する。buildメソッドにてPoolingHttpClientConnectionManagerのインスタンスを生成する。 |
(5)
|
org.apache.hc.client5.http.impl.classic.HttpClientBuilder.createメソッドにてHttpClientBuilderを生成する。生成した
HttpClientBuilderのsetConnectionManagerメソッドに、(4)で生成したPoolingHttpClientConnectionManagerのインスタンスを設定する。buildメソッドにてHttpClientのインスタンスを生成する。 |
(6)
|
HttpComponentsClientHttpRequestFactoryのコンストラクタに、(5)で生成したHttpClientのインスタンスを設定して、HttpComponentsClientHttpRequestFactoryのインスタンスを生成する。 |
(7)
|
HttpComponentsClientHttpRequestFactoryのsetReadTimeoutメソッドに、(2)の読み込みタイムアウト時間を設定する。setReadTimeoutメソッドで設定した読み込みタイムアウト時間は、org.apache.hc.client5.http.config.RequestConfig.BuilderのsetResponseTimeoutに設定される。 |
(8)
|
RestTemplateのコンストラクタに、生成したHttpComponentsClientHttpRequestFactoryを設定する。HttpComponentsClientHttpRequestFactoryで設定したタイムアウト値を超えた場合はorg.springframework.web.client.ResourceAccessExceptionが発生する。 |
applicationContext.xml
<!-- (1) -->
<bean id="timeoutConnectionConfigBuilder" class="org.apache.hc.client5.http.config.ConnectionConfig" factory-method="custom" >
<property name="connectTimeout" >
<bean class="org.apache.hc.core5.util.Timeout" factory-method="ofMilliseconds">
<constructor-arg value="${api.connectTimeout: 2000}" />
</bean>
</property>
</bean>
<!-- (2) -->
<bean id="timeoutConnectionConfig" factory-bean="timeoutConnectionConfigBuilder" factory-method="build" />
<!-- (3) -->
<bean id="timeoutPoolManagerBuilder" class="org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder" factory-method="create">
<property name="defaultConnectionConfig" ref="timeoutConnectionConfig" />
</bean>
<!-- (4) -->
<bean id="timeoutPoolManager" factory-bean="timeoutPoolManagerBuilder" factory-method="build" />
<!-- (5) -->
<bean id="timeoutHttpClientBuilder" class="org.apache.hc.client5.http.impl.classic.HttpClientBuilder" factory-method="create">
<property name="connectionManager" ref="timeoutPoolManager" />
</bean>
<!-- (6) -->
<bean id="timeoutHttpClient" factory-bean="timeoutHttpClientBuilder" factory-method="build" />
<!-- (7) -->
<bean id="clientHttpRequestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<constructor-arg ref="timeoutHttpClient" />
<property name="readTimeout" value="#{T(java.time.Duration).ofMillis(${api.readTimeout: 2000})}" />
</bean>
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg ref="clientHttpRequestFactory" /> // (8)
</bean>
項番 |
説明 |
|---|---|
(1)
|
org.apache.hc.client5.http.config.ConnectionConfig.customメソッドにてBuilderを生成する。生成した
BuilderのconnectTimeoutプロパティに、サーバとの接続タイムアウトの時間(ミリ秒)を設定する。 |
(2)
|
BuilderのbuildメソッドにてConnectionConfigのインスタンスを生成する。 |
(3)
|
org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder.createメソッドにてPoolingHttpClientConnectionManagerBuilderを生成する。生成した
PoolingHttpClientConnectionManagerBuilderのdefaultConnectionConfigプロパティに、(2)で生成したConnectionConfigのインスタンスを設定する。 |
(4)
|
PoolingHttpClientConnectionManagerBuilderのbuildメソッドにてPoolingHttpClientConnectionManagerのインスタンスを生成する。 |
(5)
|
org.apache.hc.client5.http.impl.classic.HttpClientBuilder.createメソッドにてHttpClientBuilderを生成する。生成した
HttpClientBuilderのconnectionManagerプロパティに、(4)で生成したPoolingHttpClientConnectionManagerのインスタンスを設定する。 |
(6)
|
HttpClientBuilderのbuildメソッドにてHttpClientのインスタンスを生成する。 |
(7)
|
HttpComponentsClientHttpRequestFactoryのコンストラクタに、(6)で生成したHttpClientのインスタンスを設定して、HttpComponentsClientHttpRequestFactoryのインスタンスを生成する。HttpComponentsClientHttpRequestFactoryのreadTimeoutプロパティに、レスポンスデータの読み込みタイムアウトの時間(ミリ秒)を設定する。readTimeoutプロパティで設定した読み込みタイムアウト時間は、org.apache.hc.client5.http.config.RequestConfig.BuilderのsetResponseTimeoutに設定される。 |
(8)
|
RestTemplateのコンストラクタに、生成したHttpComponentsClientHttpRequestFactoryを設定する。HttpComponentsClientHttpRequestFactoryで設定したタイムアウト値を超えた場合はorg.springframework.web.client.ResourceAccessExceptionが発生する。 |
Note
タイムアウト発生時の起因例外
HttpComponentsClientHttpRequestFactoryで設定したタイムアウト値を超えた場合は、タイムアウトの種類に関わらずorg.springframework.web.client.ResourceAccessExceptionが発生する。したがって、タイムアウトの種類に応じて例外ハンドリングを行いたい場合は、ResourceAccessExceptionの起因例外の型を確認する必要がある。ResourceAccessExceptionの起因例外として、接続タイムアウト発生時はorg.apache.hc.client5.http.ConnectTimeoutException、読み込みタイムアウト発生時はjava.net.SocketTimeoutExceptionが設定されているため、これらの型を確認することでタイムアウトの種類を判別することができる。なお、上記挙動に関しては使用するClientHttpRequestFactoryの実装によって異なるため、ClientHttpRequestFactoryが利用するHttpClientの仕様を確認してから実装を行うこと。
5.2.2.9. ファイルアップロード(マルチパートリクエスト)¶
ファイルアップロードの実装例
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に設定する。MultiValueMapにString値以外が設定されている場合、Content-TypeヘッダはFormHttpMessageConverterによってmultipart/form-dataが設定される。したがって、上記の例では省略しても問題はない。
|
(4)
|
アップロードするファイルが格納されている
MultiValueMapをリクエストボディに設定する。 |
Note
Spring Frameworkが提供するResourceクラスについて
Spring Frameworkはリソースを表現するインタフェースとしてorg.springframework.core.io.Resourceを提供しており、ファイルをアップロードする際に使用することができる。
Resourceインタフェースの主な実装クラスは以下の通りである。
org.springframework.core.io.PathResourceorg.springframework.core.io.FileSystemResourceorg.springframework.core.io.ClassPathResourceorg.springframework.core.io.UrlResourceorg.springframework.core.io.InputStreamResource(ファイル名をサーバに連携できない)org.springframework.core.io.ByteArrayResource(ファイル名をサーバに連携できない)
5.2.2.10. ファイルダウンロード¶
InputStreamを使用してレスポンスボディを少しずつファイルに書き出す方法と、byte配列でレスポンスボディを一括取得する方法がある。byte配列で一括取得する方法は、ダウンロードするファイルのサイズによっては、java.lang.OutOfMemoryErrorが発生する可能性があるため、基本的にはInputStreamを使用した実装を推奨する。byte配列で一括取得する実装を検討するとよい。InputStreamを使用してファイルに書き出す方法
InputStreamで読み込み、読み込んだ内容をファイルに書き出す実装例を示す。ResponseExtractorを使用したダウンロードの実装例である。インポート宣言
import org.springframework.util.FileCopyUtils;
メソッド内部
// (1)
final ResponseExtractor<File> responseExtractor =
new ResponseExtractor<File>() {
// (2)
@Override
public File extractData(ClientHttpResponse response)
throws IOException {
File rcvFile = File.createTempFile("rcvFile", "zip"); // (3)
FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile)); // (4)
return rcvFile; // (5)
}
};
// (6)
File rcvFile = this.restTemplate.execute(targetUri,
HttpMethod.GET, null, responseExtractor);
// omitted
項番 |
説明 |
|---|---|
(1)
|
レスポンス取得後処理をカスタマイズするため、
ResponseExtractorの実装を作成する。 |
(2)
|
extractDataメソッドを実装し、レスポンス取得後処理を定義する。 |
(3)
|
出力するファイルを生成する。
|
(4)
|
レスポンスボディを
InputStreamで読み込み、FileCopyUtilsを使用して、(3)で生成したファイルにレスポンスボディを少しずつ書き出す。 |
(5)
|
ファイル作成後、作成したファイルを返却する。
Note HTTPヘッダや、ステータスコードも返却する場合 HTTPヘッダや、ステータスコードも含めて返却したい場合は、
|
(6)
|
RestTemplate.executeメソッドを使用してファイルのダウンロードを行い、出力したファイルを取得する。 |
byte配列で一括取得する方法
byte配列で一括取得する実装例である。byte[] downloadContent =
restTemplate.getForObject(uri, byte[].class); //(1)
項番 |
説明 |
|---|---|
(1)
|
ダウンロードファイルを指定したデータ型で扱う。
上記の例では、ダウンロードしたファイルをバイト配列で取得する。
|
Warning
byte配列でファイルをダウンロードする際の注意点
サイズの大きなファイルをデフォルトで登録されているHttpMessageConverterを使用してbyte配列で取得すると、java.lang.OutOfMemoryErrorが発生する可能性がある。また、org.springframework.core.io.Resourceで取得した場合も、メッセージ変換で利用されるResourceHttpMessageConverterがByteArrayResourceを生成する際にレスポンスボディをbyte配列で取得するため、同様の事象が発生する。
5.2.3. How to extend(同期通信)¶
5.2.3.1. URL生成処理をカスタマイズする方法(UriBuilderFactory)¶
UriBuilderFactoryおよびUriBuilderの実装を拡張するすることで実現する。UriBuilderFactoryやUriBuilderを利用せず、個別のリクエスト送信処理の中でURLをカスタマイズすることも可能だが、そうでない場合は、UriBuilderFactoryやUriBuilderを利用することで実装の重複を避け、コードのメンテンス性を落とさずにカスタマイズが可能である。UriBuilderFactoryおよびUriBuilderは日付の利用に関係なく、リクエスト先URLの生成処理一般のためのカスタマイズポイントとして機能する。5.2.3.1.1. URIテンプレート変数値のフォーマット変換¶
LocalDate)を、特定のフォーマット文字列に変換するUriBuilderFactoryの実装例である。URIテンプレート:
http://localhost:8080/api/order/{date}URIテンプレート変数の値:
LocalDate.of(2025, 1, 1);送信時のURL:
http://localhost:8080/api/order/20250101
UriBuilderFactoryの拡張
// (1)
public class CustomUriBuilderFactory extends DefaultUriBuilderFactory {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
// Default Constructor
public CustomUriBuilderFactory() {
super();
}
// Constructor with baseUri
public CustomUriBuilderFactory(String baseUriTemplate) {
super(baseUriTemplate);
}
// Constructor with UriComponentsBuilder
public CustomUriBuilderFactory(UriComponentsBuilder baseUri) {
super(baseUri);
}
// (2)
@Override
public UriBuilder uriString(String uriTemplate) {
return new CustomUriBuilder(super.uriString(uriTemplate));
}
// (2)
@Override
public UriBuilder builder() {
return new CustomUriBuilder(super.builder());
}
// (3)
private class CustomUriBuilder implements UriBuilder {
private final UriBuilder delegate;
public CustomUriBuilder(UriBuilder delegate) {
this.delegate = delegate;
}
// (4)
@Override
public URI build(Map<String, ?> uriVariables) {
Map<String, Object> convertedUriVariables =
uriVariables.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, this::convert));
return delegate.build(convertedUriVariables);
}
@Override
public URI build(Object... uriVariables) {
Object[] convertedUriVariables =
Arrays.stream(uriVariables)
.map(this::convert)
.toArray();
return delegate.build(convertedUriVariables);
}
// (5)
private Object convert(Object value) {
if(value instanceof LocalDate date) {
return FORMATTER.format(date);
}
return value;
}
// Delegate all other methods to the original UriBuilder
// (6)
@Override
public UriBuilder scheme(String scheme) {
return delegate.scheme(scheme);
}
@Override
public UriBuilder userInfo(String userInfo) {
return delegate.userInfo(userInfo);
}
// ... other methods from UriBuilder
}
}
項番 |
説明 |
|---|---|
(1)
|
UriBuilderFactoryの実装クラスとして、CustomUriBuilderFactoryを生成する。上記の例では、
DefaultUriBuilderFactoryを拡張した実装クラスを作成して、必要なメソッドのみをオーバーライドしている。コンストラクタに関しては、
DefaultUriBuilderFactoryと同様のコンストラクタを用意している。 |
(2)
|
UriBuilderを生成するメソッドをオーバーライドして、独自のUriBuilderを生成するよう実装を行う。変更箇所以外は
DefaultUriBuilderの実装をそのまま使用するため、CustomUriBuilderのコンストラクタ引数にてDefaultUriBuilderを引き渡すようにしている。 |
(3)
|
UriBuilderの実装クラスとしてCustomUriBuilderを生成する。 |
(4)
|
buildメソッドにパラメータの変換処理を作成する。上記の例では、URIテンプレート変数の値が設定されているコレクションを受け取り、全ての値に対して変換処理を行い返却を行っている。
なお、カスタマイズした変換処理以外は
DefaultUriBuilderの実装をそのまま使用するため、DefaultUriBuilderのbuildメソッドを最後に実行している。 |
(5)
|
(4)の各要素で呼び出す変換処理を実装する。
上記の例では、
LocalDate型の場合のみ、指定したフォーマット文字列に変換して返却するようにしている。 |
(6)
|
変換処理以外のメソッドは、
DefaultUriBuilderの実装をそのまま使用するため、DefaultUriBuilderのメソッドをそのまま呼び出すように実装を行う。 |
Bean定義ファイルの定義例
ApplicationContextConfig.java
// (1)
@Bean("customUriBuilderFactory")
public CustomUriBuilderFactory customUriBuilderFactory() {
return new CustomUriBuilderFactory();
}
@Bean("restTemplate")
public RestTemplate restTemplate() {
var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
restTemplate.setUriTemplateHandler(customUriBuilderFactory()); // (2)
return restTemplate;
}
項番 |
説明 |
|---|---|
(1)
|
CustomUriBuilderFactoryのBeanを定義する。 |
(2)
|
RestTemplateのsetUriTemplateHandlerメソッドに、CustomUriBuilderFactoryのBeanを設定する。 |
applicationContext.xml
<!-- (1) -->
<bean id="customUriBuilderFactory" class="org.example.restclient.custom.CustomUriBuilderFactory" />
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" />
</constructor-arg>
<property name="uriTemplateHandler" ref="customUriBuilderFactory" /> <!-- (2) -->
</bean>
項番 |
説明 |
|---|---|
(1)
|
CustomUriBuilderFactoryのBeanを定義する。 |
(2)
|
RestTemplateのuriTemplateHandlerプロパティに、CustomUriBuilderFactoryのBeanを設定する。 |
5.2.3.2. リクエスト生成処理のカスタマイズ方法(ClientHttpRequestFactory)¶
ClientHttpRequestFactoryを使用したリクエスト生成処理のカスタマイズ方法について説明する。5.2.3.2.1. カスタマイズしたキーストアファイルの利用(SSL/TLS)¶
FactoryBeanの実装例
RestTemplateに設定するClientHttpRequestFactoryをorg.springframework.beans.factory.FactoryBeanにて実装する。HttpClientConnectionManagerやHttpClientの設定値は業務要件に応じ適切に設定されたい。import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
public class RequestFactoryBean implements
FactoryBean<ClientHttpRequestFactory>,
DisposableBean { // (18)
private String keyStoreFileName;
private char[] keyStorePassword;
private HttpComponentsClientHttpRequestFactory factory; // (18)
@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);
// @formatter:off
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() // (2)
.setTlsSocketStrategy(new DefaultClientTlsStrategy(sslContext)) // (3)
.setDefaultTlsConfig( // (4)
TlsConfig.custom()
.setSupportedProtocols(TLS.V_1_3, TLS.V_1_2)
.setHandshakeTimeout(Timeout.ofMilliseconds(1L))
.build())
.setDefaultSocketConfig( // (5)
SocketConfig.custom()
.setSoTimeout(Timeout.ofMinutes(1L))
.build())
.setMaxConnTotal(1) // (6)
.setMaxConnPerRoute(1) // (7)
.setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT) // (8)
.setConnPoolPolicy(PoolReusePolicy.LIFO) // (9)
.setDefaultConnectionConfig( // (10)
ConnectionConfig.custom()
.setTimeToLive(TimeValue.ofMinutes(1L)) // (11)
.setConnectTimeout(Timeout.ofSeconds(5L)) // (12)
.build())
.build();
// @formatter:on
// @formatter:off
CloseableHttpClient httpClient = HttpClients.custom() // (13)
.setConnectionManager(connectionManager) // (14)
.setDefaultRequestConfig(
RequestConfig.custom()
.setResponseTimeout(Timeout.ofSeconds(10L)) // (15)
.setCookieSpec(StandardCookieSpec.STRICT) // (16)
.build())
.build();
// @formatter:on
// (17)
this.factory = new HttpComponentsClientHttpRequestFactory(httpClient);
return this.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;
}
// (18)
@Override
public void destroy() throws Exception {
if (this.factory != null) {
this.factory.destroy();
}
}
}
項番 |
説明 |
|---|---|
(1)
|
後述のBean定義で指定されたキーストアファイルのファイル名とパスワードを元に、SSLコンテキストを作成する。
使用するSSL自己署名証明書のキーストアファイルは、クラスパス上に配置する。
|
(2)
|
(1)で作成したSSLコンテキストを設定するためのHttpClientConnectionManagerを作成する。
ここでは、実装クラスとして
org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerを指定している。 |
(3)
|
(1)で作成したSSLコンテキストを設定する。
|
(4)
|
Tls Configのデフォルト値を設定する。
Socketから提供されるInputStreamのread()のブロック時間がHandshakeTimeoutを越えた場合、通常、java.net.SocketTimeoutExceptionが発生するが、TCPコネクション確立後、SSLハンドシェイクを行っている間にこのタイムアウトが起きた場合には、org.apache.hc.client5.http.ConnectTimeoutExceptionが発生する。Note HandshakeTimeoutはSSLハンドシェイクが完了する十分な長さを確保すること。 SSLハンドシェイク完了前に そのため、SSLハンドシェイク中に通信障害や通信相手のハードウェア障害等に遭遇し通信相手からのパケットが届かない時間が長く続く場合(クライアント側で通信の継続を諦めなければならない場合)以外においては、 SSLハンドシェイク完了後のHTTPSリクエストに対するレスポンスのタイムアウトには、後述の
という具合に使い分けること。なお、 |
(5)
|
Socket Configのデフォルト値を設定する。
Note SoTimeoutの設定について
そのため、SSLハンドシェイクのタイムアウト設定を行う場合は、(4)の |
(6)
|
最大合計接続数を設定する。
最大合計接続数を超える場合、後続処理はコネクションの取得を待機する。
|
(7)
|
宛先(ホスト名 + ポート番号 及び スキーマ定義)ごとの最大接続数を設定する。
最大接続数を超える場合、後続処理はコネクションの取得を待機する。
|
(8)
|
プール同時実行ポリシーを設定する。
|
(9)
|
プールされたコネクションの再利用ポリシーを設定する。
|
(10)
|
コネクションに関連するデフォルト値を設定する。
|
(11)
|
コネクションが接続されてから切断されるまでの最大生存時間を設定する。
使用状況にかかわらず、ConnectionTimeToLiveを越えた場合にコネクションを破棄する。
|
(12)
|
新しい接続が確立されるまでのタイムアウトのデフォルト値を設定する。
接続の確立にConnectTimeout以上かかった場合、
org.apache.hc.client5.http.HttpHostConnectExceptionが発生する。 |
(13)
|
作成したSSLコンテキストを利用する
org.apache.hc.client5.http.impl.classic.CloseableHttpClientを作成する。 |
(14)
|
(2)で作成した
HttpClientConnectionManagerを設定する。 |
(15)
|
レスポンスタイムアウトのデフォルト値を設定する。
レスポンス返却にResponseTimeout以上かかった場合、
java.net.SocketTimeoutExceptionが発生する。 |
(16)
|
|
(17)
|
作成した
HttpClientを利用するClientHttpRequestFactoryを作成する。 |
(18)
|
FactoryBeanで生成したオブジェクトのライフサイクルはDIコンテナで管理されないため、破棄時に特定の処理を実行するにはFactoryBeanにDisposableBeanインタフェースのdestroyメソッドを実装する必要がある。ここでは、
getObjectメソッドで生成した HttpComponentsClientHttpRequestFactoryのdestroyメソッドを呼び出し、HttpClientをクローズしている。このため、生成したオブジェクトを変数に保持している。 |
Note
通信先のアプリケーションがTLS 1.2以前のバージョンにしか対応していない等の理由により、使用するTLSのバージョンをJVMレベルで変更するにはHTTP通信におけるTLS(Transport Layer Security) v1.3のサポートを参照されたい。
ただし、JVMレベルで設定してしまうと一つのクライアントアプリからTLS 1.2とTLS 1.3を利用した別々のアプリケーションに接続するような要件を実現することができない。このような場合は、HttpClientごとに利用するTLSのバージョンを指定するような実装を検討されたい。
Bean定義ファイルの定義例
SSL自己署名証明書を使用したSSL通信を行うClientHttpRequestFactoryを、RestTemplateに設定する。
ApplicationContextConfig.java
@Value("${rscl.keystore.filename}")
private String keystoreFilename;
@Value("${rscl.keystore.password}")
private String keystorePassword;
// omitted
@Bean("httpsRestTemplate")
public RestTemplate httpsRestTemplate() throws Exception {
return new RestTemplate(httpsRequestFactoryBean().getObject()); // (1)
}
// (1)
@Bean("httpsRequestFactoryBean")
public RequestFactoryBean httpsRequestFactoryBean() {
var factory = new RequestFactoryBean();
factory.setKeyStoreFileName(keystoreFilename);
factory.setKeyStorePassword(keystorePassword.toCharArray());
return factory;
}
項番 |
説明 |
|---|---|
(1)
|
作成した
RequestFactoryBeanをRestTemplateのコンストラクタに指定する。RequestFactoryBeanには、キーストアファイルのファイル名とパスワードを渡す。 |
applicationContext.xml
<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には、キーストアファイルのファイル名とパスワードを渡す。 |
RESTクライアントの使用方法
RestTemplateの使い方については、SSL自己署名証明書を使用しない場合と同様であるため、以下を参照されたい。
5.2.3.2.2. HTTP Proxyサーバの設定方法¶
RestTemplateのBean定義にてHTTP Proxyサーバの設定が必要である。RestTemplate毎にHTTP Proxyサーバの設定を行う例を紹介する。ClientHttpRequestFactoryインタフェースの実装クラスであるHttpComponentsClientHttpRequestFactoryを利用して設定を行う。5.2.3.2.2.1. Proxy認証を行わない場合の設定¶
資格情報が不要なHTTP Proxyサーバの接続先の指定については、以下の通り実装を行う。
Bean定義ファイル
ApplicationContextConfig.java
// (2)
@Value("${rscl.http.proxyHost}")
private String httpProxyHost;
// (3)
@Value("${rscl.http.proxyPort}")
private int httpProxyPort;
// (1)
@Bean
public HttpHost httpHost() {
return new HttpHost(httpProxyHost, httpProxyPort); // (2) (3)
}
// (4)
@Bean("proxyHttpClientBuilder")
public HttpClientBuilder proxyHttpClientBuilder() {
HttpClientBuilder bean = HttpClientBuilder.create();
bean.setProxy(httpHost()); // (5)
return bean;
}
// (6)
@Bean("httpComponentsClientHttpRequestFactory")
public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(proxyHttpClientBuilder().build()); // (7)
}
// (8)
@Bean("proxyRestTemplate")
public RestTemplate proxyRestTemplate() {
return new RestTemplate(httpComponentsClientHttpRequestFactory()); // (9)
}
項番 |
説明 |
|---|---|
(1)
|
org.apache.hc.core5.http.HttpHostにHTTP Proxyサーバの設定を行う。 |
(2)
|
HttpHostのコンストラクタ引数のhostnameに、プロパティファイルに設定されたキーrscl.http.proxyHostの値をHTTP Proxyサーバのホスト名として設定する。 |
(3)
|
HttpHostのコンストラクタ引数のportに、プロパティファイルに設定されたキーrscl.http.proxyPortの値をHTTP Proxyサーバのポート番号として設定する。 |
(4)
|
org.apache.hc.client5.http.impl.classic.HttpClientBuilderを使用し、org.apache.hc.client5.http.classic.HttpClientの設定を行う。 |
(5)
|
HttpClientBuilderのsetProxyメソッドに、HTTP Proxyサーバの設定を行ったorg.apache.hc.core5.http.HttpHostを設定する。 |
(6)
|
HttpComponentsClientHttpRequestFactoryにプロキシ設定を行ったHttpClientを設定する。 |
(7)
|
HttpComponentsClientHttpRequestFactoryのコンストラクタの引数に、HttpClientBuilderから生成したHttpClientを設定する。 |
(8)
|
RestTemplateのBean定義を行う。 |
(9)
|
RestTemplateのコンストラクタの引数に、(6)で生成したHttpComponentsClientHttpRequestFactoryを設定する。 |
applicationContext.xml
<!-- (4) -->
<bean id="proxyHttpClientBuilder" class="org.apache.hc.client5.http.impl.classic.HttpClientBuilder" factory-method="create">
<!-- (5) -->
<property name="proxy">
<!-- (1) -->
<bean class="org.apache.hc.core5.http.HttpHost">
<!-- (2) -->
<constructor-arg type="java.lang.String" name="hostname" value="${rscl.http.proxyHost}" />
<!-- (3) -->
<constructor-arg type="int" name="port" value="${rscl.http.proxyPort}" />
</bean>
</property>
</bean>
<!-- (8) -->
<bean id="proxyRestTemplate" class="org.springframework.web.client.RestTemplate" >
<!-- (9) -->
<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.hc.core5.http.HttpHostにHTTP Proxyサーバの設定を行う。 |
(2)
|
HttpHostのコンストラクタ引数のhostnameに、プロパティファイルに設定されたキーrscl.http.proxyHostの値をHTTP Proxyサーバのホスト名として設定する。 |
(3)
|
HttpHostのコンストラクタ引数のportに、プロパティファイルに設定されたキーrscl.http.proxyPortの値をHTTP Proxyサーバのポート番号として設定する。 |
(4)
|
org.apache.hc.client5.http.impl.classic.HttpClientBuilderを使用し、org.apache.hc.client5.http.classic.HttpClientの設定を行う。 |
(5)
|
HttpClientBuilderのproxyプロパティに、HTTP Proxyサーバの設定を行ったorg.apache.hc.core5.http.HttpHostを設定する。Warning オーバーロードされている場合に誤ったコンストラクタが使用されることがある 当実装例は、本来であれば以下の様に記述することが可能である。
ただし、Spring#31871で報告されているように、コンストラクタがオーバーロードされている場合に想定とは異なるコンストラクタが使用されてしまう可能性がある。 そのためtype属性を設定し、明示的に使用するコンストラクタを決定している。 |
(6)
|
HttpComponentsClientHttpRequestFactoryにプロキシ設定を行ったHttpClientを設定する。 |
(7)
|
HttpComponentsClientHttpRequestFactoryのコンストラクタの引数に、HttpClientBuilderから生成したHttpClientを設定する。 |
(8)
|
RestTemplateのBean定義を行う。 |
(9)
|
RestTemplateのコンストラクタの引数に、(6)で生成したHttpComponentsClientHttpRequestFactoryを設定する。 |
5.2.3.2.2.2. Proxy認証を行う場合の設定¶
org.apache.http.impl.client.BasicCredentialsProviderを使用し資格情報を設定する。BasicCredentialsProviderのsetCredentialsメソッドが引数を2つ取るため、セッターインジェクションを利用してBeanを生成することができない。org.springframework.beans.factory.FactoryBeanを利用してBeanを生成する。FactoryBeanクラス
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.impl.auth.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)
var authScope = new AuthScope(this.host, this.port);
// (7)
char[] passwordCharArray = this.password == null ? null
: this.password.toCharArray();
var usernamePasswordCredentials = new UsernamePasswordCredentials(this.userName, passwordCharArray);
// (8)
var 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定義ファイル
ApplicationContextConfig.java
// (1)
@Bean
public BasicCredentialsProviderFactoryBean basicCredentialsProviderFactoryBean() {
return new BasicCredentialsProviderFactoryBean();
}
@Bean
public HttpHost httpHost() {
return new HttpHost(httpProxyHost, httpProxyPort);
}
@Bean("proxyHttpClientBuilder")
public HttpClientBuilder proxyHttpClientBuilder() throws Exception {
HttpClientBuilder bean = HttpClientBuilder.create();
bean.setDefaultCredentialsProvider(basicCredentialsProviderFactoryBean().getObject()); // (1)
bean.setProxy(httpHost());
return bean;
}
@Bean("httpComponentsClientHttpRequestFactory")
public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(proxyHttpClientBuilder()
.build());
}
@Bean("proxyRestTemplate")
public RestTemplate proxyRestTemplate() {
return new RestTemplate(httpComponentsClientHttpRequestFactory());
}
項番 |
説明 |
|---|---|
(1)
|
HttpClientBuilderのsetDefaultCredentialsProviderメソッドに、BasicCredentialsProviderを設定する。BasicCredentialsProviderは、FactoryBeanを実装したBasicCredentialsProviderFactoryBeanを使用しBeanを作成する。 |
applicationContext.xml
<bean id="proxyHttpClientBuilder" class="org.apache.hc.client5.http.impl.classic.HttpClientBuilder" factory-method="create">
<!-- (1) -->
<property name="defaultCredentialsProvider">
<bean class="com.example.restclient.BasicCredentialsProviderFactoryBean" />
</property>
<property name="proxy">
<bean class="org.apache.hc.core5.http.HttpHost">
<constructor-arg type="java.lang.String" name="hostname" value="${rscl.http.proxyHost}" />
<constructor-arg type="int" name="port" 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.3.3. メッセージ変換処理の設定(HttpMessageConverter)¶
5.2.3.3.1. カスタムメッセージコンバータの登録¶
HttpMessageConverterで電文変換の要件を満たせない場合は、カスタマイズした HttpMessageConverterを作成、登録することで要件に対応する事ができる。HttpMessageConverterは利用されなくなる。このため、カスタムメッセージコンバータ以外にも、要件を満たすためのメッセージコンバータが必要な場合は、必要なHttpMessageConverter実装をすべて登録する必要がある。カスタムメッセージコンバータの作成
import java.io.IOException;
import java.lang.reflect.Type;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.AbstractGenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
// (1)
public class CustomHttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
public CustomHttpMessageConverter() {
// (2)
super(new MediaType("text", "custom-format"), new MediaType("application", "custom-format"));
}
// (3)
@Override
protected void writeInternal(Object t, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// omitted
}
// (3)
@Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// omitted
}
// (3)
@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// omitted
}
// omitted
}
項番 |
説明 |
|---|---|
(1)
|
org.springframework.http.converter.AbstractGenericHttpMessageConverterを継承した実装クラスを作成する。AbstractGenericHttpMessageConverterはGenericHttpMessageConverterインターフェースを実装した抽象クラスであり、「コレクション形式のデータ取得」で説明したParameterizedTypeReferenceを利用したコレクション型を扱うことができる。 |
(2)
|
本コンバータが対応する
MediaTypeを設定する。なお、複数の
MediaTypeに対応する場合は、配列で複数指定する必要がある。 |
(3)
|
要件にあわせて変換処理を実装する。
メソッドの説明は、AbstractGenericHttpMessageConverterのJavadocを参照されたい。
|
Bean定義ファイルの定義例
ApplicationContextConfig.java
// (1)
@Bean("customHttpMessageConverter")
public CustomHttpMessageConverter customHttpMessageConverter() {
return new CustomHttpMessageConverter();
}
@Bean("restTemplate")
public RestTemplate restTemplate() {
var bean = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
List<HttpMessageConverter<?>> converters = new ArrayList<>();
// omitted: Default HttpMessageConverters registration
converters.add(customHttpMessageConverter()); // (2)
bean.setMessageConverters(converters); // (3)
return bean;
}
項番 |
説明 |
|---|---|
(1)
|
カスタムメッセージコンバータのBeanを定義する。
|
(2)
|
Listに(1)で定義したBeanを追加する。カスタムメッセージコンバータ以外の
HttpMessageConverterの登録は省略しているが、必要に応じて登録を行うこと。 |
(3)
|
RestTemplateのsetMessageConvertersメソッドを使用して、HttpMessageConverterのリストを設定する。 |
applicationContext.xml
<!-- (1) -->
<bean id="customHttpMessageConverter" class="com.example.restclient.CustomHttpMessageConverter" />
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" />
</constructor-arg>
<property name="messageConverters"> <!-- (3) -->
<list>
<!-- omitted: Default HttpMessageConverters registration -->
<ref bean="customHttpMessageConverter" /> <!-- (2) -->
</list>
</property>
</bean>
項番 |
説明 |
|---|---|
(1)
|
カスタムメッセージコンバータのBeanを定義する。
|
(2)
|
Listに(1)で定義したBeanを追加する。カスタムメッセージコンバータ以外の
HttpMessageConverterの登録は省略しているが、必要に応じて登録を行うこと。 |
(3)
|
RestTemplateのmessageConvertersプロパティを使用して、HttpMessageConverterのリストを設定する。 |
5.2.3.4. リクエスト送信前後の共通処理の適用(ClientHttpRequestInterceptor)¶
ClientHttpRequestInterceptorを使用することで、サーバとの通信処理の前後に任意の処理を実行させることができる。5.2.3.4.1. リクエスト送信前後のロギング処理¶
サーバとの通信ログを出力したい場合は、以下のような実装を行う。
通信ログ出力の実装例
ClientHttpRequestInterceptorの実装
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.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()) {
var requestBody = new String(body, StandardCharsets.UTF_8);
// (2)
logger.info("Request Header {}", request.getHeaders());
logger.info("Request Body {}", requestBody);
}
ClientHttpResponse response = execution.execute(request, body); // (3)
if (logger.isInfoEnabled()) {
// (4)
logger.info("Response Header {}", response.getHeaders());
logger.info("Response Status Code {}", response.getStatusCode());
}
return response; // (5)
}
}
項番 |
説明 |
|---|---|
(1)
|
org.springframework.http.client.ClientHttpRequestInterceptorインタフェースを実装する。 |
(2)
|
リクエスト送信前の共通処理を実装する。
上記の実装例では、リクエストヘッダとリクエストボディの内容をログに出力している。
|
(3)
|
interceptメソッドの引数として受け取ったorg.springframework.http.client.ClientHttpRequestExecutionのexecuteメソッドを実行し、リクエストの送信を行う。 |
(4)
|
レスポンス受信後の共通処理を実装する。
上記の実装例では、レスポンスヘッダとステータスコードの内容をログに出力している。
|
(5)
|
(3)で受信したレスポンスを返却する。
|
Bean定義ファイルの定義例
ApplicationContextConfig.java
// (1)
@Bean("loggingInterceptor")
public LoggingInterceptor loggingInterceptor() {
return new LoggingInterceptor();
}
@Bean("restTemplate")
public RestTemplate restTemplate() {
var bean = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
bean.setInterceptors(List.of(loggingInterceptor())); // (2)
return bean;
}
項番 |
説明 |
|---|---|
(1)
|
ClientHttpRequestInterceptorの実装クラスのBean定義を行う。 |
(2)
|
RestTemplateのsetInterceptorsメソッドにClientHttpRequestInterceptorのListを設定する。 |
applicationContext.xml
<!-- (1) -->
<bean id="loggingInterceptor" class="com.example.restclient.LoggingInterceptor" />
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" />
</constructor-arg>
<property name="interceptors"><!-- (2) -->
<list>
<ref bean="loggingInterceptor" />
</list>
</property>
</bean>
項番 |
説明 |
|---|---|
(1)
|
ClientHttpRequestInterceptorの実装クラスのBean定義を行う。 |
(1)
|
interceptorsプロパティにClientHttpRequestInterceptorのListをインジェクションする。 |
5.2.3.4.2. 複数のClientHttpRequestInterceptorを適用する方法¶
ClientHttpRequestInterceptorを適用するには、以下のような実装を行う。Bean定義ファイルの定義例
ApplicationContextConfig.java
@Bean("loggingInterceptor")
public LoggingInterceptor loggingInterceptor() {
return new LoggingInterceptor();
}
@Bean("customInterceptor")
public CustomInterceptor customInterceptor() {
return new CustomInterceptor();
}
@Bean("restTemplate")
public RestTemplate restTemplate() {
var bean = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
bean.setInterceptors(List.of(loggingInterceptor(), customInterceptor())); // (1)
return bean;
}
項番 |
説明 |
|---|---|
(1)
|
RestTemplateのsetInterceptorsメソッドにClientHttpRequestInterceptorのListを設定する。Listに追加した順番でClientHttpRequestInterceptorのチェーンが実行される。上記の例では、リクエスト送信前はloggingInterceptor、customInterceptorの順に実行され、レスポンス受信後はcustomInterceptor、loggingInterceptorの順に実行される。
|
applicationContext.xml
<bean id="loggingInterceptor" class="com.example.restclient.LoggingInterceptor" />
<bean id="customInterceptor" class="com.example.restclient.CustomInterceptor" />
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" />
</constructor-arg>
<property name="interceptors"><!-- (1) -->
<list>
<ref bean="loggingInterceptor" />
<ref bean="customInterceptor" />
</list>
</property>
</bean>
項番 |
説明 |
|---|---|
(1)
|
RestTemplateのinterceptorsプロパティにClientHttpRequestInterceptorのListを設定する。Listに追加した順番でClientHttpRequestInterceptorのチェーンが実行される。上記の例では、リクエスト送信前はloggingInterceptor、customInterceptorの順に実行され、レスポンス受信後はcustomInterceptor、loggingInterceptorの順に実行される。
|
5.2.4. Appendix¶
5.2.4.1. RestTemplateにてURIテンプレートを扱う方法と実装例¶
RestTemplateにてURIテンプレートを扱うには、RestTemplateの呼び出すメソッドにより対応方法が異なるため、メソッド毎に説明を行う。getForObjectメソッドでの使用例
getForObjectメソッドでURLを直接渡す場合は、RestTemplateの内部でUriBuilderFactoryが使用されるため、URIテンプレートとURIテンプレート変数の値を使ったURL文字列の生成が自動で行われる。フィールド宣言部
@Value("${api.serverUrl}/api/users/{userId}") // (1)
String uriStr;
メソッド内部
User user = restTemplate.getForObject(uriStr, User.class, "0001"); // (2)
項番 |
説明 |
|---|---|
(1)
|
URIテンプレートの変数{userId}は、
RestTemplateの使用時に指定の値に変換される。 |
(2)
|
URIテンプレートの変数1つ目が
getForObjectメソッドの第3引数に指定した値で置換され、『http://localhost:8080/api/users/0001』として処理される。 |
exchangeメソッドでの使用例
exchangeメソッドでRequestEntityを渡す場合は、RestTemplateにてUriBuilderFactoryは使用されないため、URIテンプレートとURIテンプレート変数の値を使ったURL文字列の生成が自動で行われない。UriComponentsBuilderを使用して明示的にURLを構築する必要がある。フィールド宣言部
@Value("${api.serverUrl}/api/users/{action}") // (1)
String uriStr;
メソッド内部
URI targetUri = UriComponentsBuilder.fromUriString(uriStr).
buildAndExpand("create").toUri(); //(2)
var user = new User();
// omitted
RequestEntity<User> requestEntity = RequestEntity
.post(targetUri)
.body(user);
ResponseEntity<User> responseEntity = restTemplate.exchange(requestEntity, User.class);
項番 |
説明 |
|---|---|
(1)
|
URIテンプレートの変数{action}は、
RestTemplateの使用時に指定の値に変換される。 |
(2)
|
UriComponentsBuilderを使用することで、URIテンプレートの変数1つ目がbuildAndExpandの引数で指定した値に置換され、『http://localhost:8080/api/users/create』のURIが作成される。詳細はUriComponentsBuilderのJavadocを参照されたい。
|
5.2.4.2. 非同期通信の利用について¶
AsyncRestTemplateが削除されており、Spring Web Reactive APIのWebClientを使用するように案内されているため、非同期通信を行う場合はWebClientを使用する必要がある。WebClientは、Spring WebFluxに含まれるRESTクライアント実装であり、非同期かつリアクティブプログラミングをサポートしたRESTクライアント実装である。AsyncRestTemplateの代替機能としてWebClientを案内しているだけであり、Spring Web Reactiveを完全にサポートしているわけではない点に注意されたい。WebClientや、WebClientで使用されるReactor Nettyに関する詳細は以下を参照されたい。WebClientに関する参考情報Reactor Nettyに関する参考情報5.2.4.2.1. 非同期通信のセットアップ¶
項番 |
手順 |
|---|---|
(1)
|
pom.xmlにSpring WebFluxの依存関係を追加する。 |
(2)
|
WebClientのBean定義を行い、DIコンテナに登録する。 |
(3)
|
WebClientを利用するコンポーネントにて、(2)のBeanをインジェクションする。 |
5.2.4.2.1.1. 依存ライブラリ設定¶
WebClientを使用するためにpom.xmlに、Spring Frameworkのspring-webfluxライブラリを追加する。pom.xmlに追加する。<dependencies>
<!-- (1) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<!-- (2) -->
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
</dependencies>
項番 |
説明 |
|---|---|
(1)
|
spring-webfluxライブラリをdependenciesに追加する。 |
(1)
|
reactor-nettyライブラリをdependenciesに追加する。 |
5.2.4.2.1.2. WebClientのBean定義¶
WebClientのBean定義を行い、DIコンテナに登録する。Bean定義の実装例(WebClientConfig.java)
@Configuration
public class WebClientConfig {
@Bean
public WebClient defaultWebClient() {
// (1)
return WebClient.builder().build();
}
}
項番 |
説明 |
||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
(1)
|
WebClient.BuilderをbuildすることでWebClientを生成しBeanとして登録する。WebClient.Builderはオプションを付けることでカスタマイズすることが可能である。設定できるオプションは以下の通り。
|
5.2.4.2.2. 非同期通信のデータ登録¶
CompletableFutureを使用した実装にて説明を行っているが、MonoやFluxのみを使用した実装も可能である。MonoやFluxの仕様については、以下を参照されたい。CompletableFutureを利用したデータの登録
java.util.concurrent.CompletableFutureは、JDKが提供する非同期処理を行うためのクラスである。WebClientなどのリアクティブAPIと統合して利用する場合は、MonoやFluxからの結果をバイパスするために利用される。MonoやFluxの処理が動作した後にCompletableFutureにデータを渡されており、CompletableFutureがリアクティブ処理を隠蔽しているため、従来型の非同期処理として実装を行える。CompletableFutureの詳しい仕様は、「CompletableFutureのJavadoc」を参照されたい。CompletableFuture<ResponseEntity<Void>> future = // (1)
webClient.post() // (2)
.uri(url)
.bodyValue(users) // (3)
.retrieve() // (4)
.toBodilessEntity() // (5)
.toFuture(); // (6)
future.handle((r, t) -> { // (7)
if (t == null) { // (8)
// 成功のレスポンスが返ってきた場合の処理を実装
} else { // (9)
// エラーが発生した場合の処理を実装
}
return null;
});
// omitted
項番 |
説明 |
|---|---|
(1)
|
返却値に
CompletableFutureを指定し、非同期通信のレスポンスを受け取る。上記の例では、レスポンスボディは不要なため
CompletableFutureの型引数には、ResponseEntity<Void>を指定している。 |
(2)
|
postメソッドを使用してHTTPメソッドにPOSTを設定する。 |
(3)
|
RequestBodyUriSpecのbodyValueメソッドを使用してリクエストボディを設定する。 |
(4)
|
RequestBodyUriSpecのretrieveメソッドを使用してレスポンスのストリームを作成する。 |
(5)
|
ResponseSpecのtoBodilessEntityメソッドを使用して、ストリームで扱うデータ型を指定する。上記の場合は、レスポンスボディを扱わないため
Mono<ResponseEntity<Void>>型のレスポンスのストリームを取得している。 |
(6)
|
MonoのtoFutureメソッドを使用して、CompletableFuture型に変換を行う。このタイミングで、内部的に
Monoのsubscribeが実行され、非同期通信が開始される。 |
(7)
|
CompletableFutureのhandleメソッドに、処理結果に応じた処理を実装する。handleメソッドは、正常終了した場合と異常終了した場合の両方で呼び出されるため、エラーが発生したかどうかは例外の有無で判断を行う。上記の場合、
handleメソッドの関数の引数には、ResponseEntityとThrowableが渡されるため、第二引数のThrowableを参照してエラーの有無を判断している。Tip 同期通信に変更したい場合は、 |
(8)
|
成功時の処理を実装する。
|
(9)
|
エラーが発生した場合の処理を実装する。
|
5.2.4.2.3. エラーハンドリング¶
5.2.4.2.3.1. サーバとの通信結果をハンドリングする方法¶
WebClientのStatusHandlerは、HTTPステータスコードをもとにエラーの判定を行うためのインタフェースである。StatusHandlerの実装は、アプリケーション全体でエラーハンドリングを行う実装と、リクエスト毎でエラーハンドリングを行う実装の2つがあるため、それぞれについて説明を行う。アプリケーション全体でエラーハンドリングを行う方法
アプリケーション全体でエラーハンドリングを行う場合は、WebClientを生成するタイミングでStatusHandlerを設定し、エラーの判定とエラー処理の実装を行う。
WebClientConfig.java
@Bean("webClient")
public WebClient webClient() {
return WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, response -> { // (1)
// エラー時の処理を実装
return Mono.error(new CustomException(response.statusCode().value()));
}).build();
}
項番 |
説明 |
|---|---|
(1)
|
WebClient.BuilderのdefaultStatusHandlerメソッドにPredicate<HttpStatusCode>と、Function<ClientResponse, Mono<? extends Throwable>>を設定する。上記の実装例では、
HttpStatusCodeがサーバエラー(5xx系)及びクライアントエラー(4xx系)の場合、独自例外にHTTPステータスコードを設定して返却するように実装している。なお、ストリーム中にエラーを返却する場合は、
Monoのerrorメソッドを使用して返却する必要がある。 |
リクエスト毎にエラーハンドリングを行う方法
リクエスト毎にエラーハンドリングを行う場合は、非同期通信を行う際にResponseSpecのonStatusメソッドを使用して、エラーの判定とエラー処理の実装を行う。
CompletableFuture<ResponseEntity<Void>> future =
webClient.post()
.uri(url)
.bodyValue(users)
.retrieve()
.onStatus(HttpStatusCode::is5xxServerError, response -> { // (1)
// エラー時の処理を実装
return Mono.error(new CustomException(response.statusCode().value()));
})
.toBodilessEntity()
.toFuture();
項番 |
説明 |
|---|---|
(1)
|
サーバエラー(5xx系)のHTTPステータスコードが返却された場合のエラーハンドリングを行う。
defaultStatusHandlerの実装と同様に、onStatusメソッドにPredicate<HttpStatusCode>とFunction<ClientResponse, Mono<? extends Throwable>>を設定する。なお、
onStatusメソッドの記載がある場合は、defaultStatusHandlerの実装より優先されて処理される。したがって、上記の実装例ではサーバエラー(5xx系)のHTTPステータスコードが返却された場合にのみ適用され、クライアントエラー(4xx系)のHTTPステータスコードが返却された場合は
defaultStatusHandlerの実装が適用される。 |
5.2.4.2.3.2. ストリーム中やStatusHandlerで発生した例外をハンドリングする方法¶
最終的なエラーのハンドリングは、CompletableFutureのhandleメソッドにてハンドリングを行う必要がある。
CompletableFuture<ResponseEntity<Void>> future =
webClient.post()
.uri(url)
.bodyValue(users)
.retrieve()
.toBodilessEntity()
.toFuture();
future.handle((r, t) -> { // (1)
if (t == null) {
// 成功のレスポンスが返ってきた場合の処理を実装
} else {
// エラーが発生した場合の処理を実装
}
return null;
});
項番 |
説明 |
|---|---|
(1)
|
CompletableFutureのhandleメソッドにエラー時の処理を実装する。非同期で動作している特性上、画面へのエラー通知は行えないため、エラー通知や復帰処理等を行う場合は
handleメソッドにて実装を行う必要がある。 |
5.2.4.2.4. リクエスト送信前の共通処理の適用¶
org.springframework.web.reactive.function.client.ExchangeFilterFunctionを使用した共通処理の実装方法について説明を行う。
5.2.4.2.4.1. リクエスト送信前の共通処理の実装¶
org.springframework.web.reactive.function.client.ExchangeFilterFunctionを実装することで、サーバとの通信処理前に任意の処理を実行させることができる。通信ログ出力の実装例
package com.example.webclient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import reactor.core.publisher.Mono;
public class WebClientExchangeFilterFunction implements ExchangeFilterFunction { // (1)
private static final Logger LOGGER = LoggerFactory.getLogger(
WebClientExchangeFilterFunction.class);
@Override
public Mono<ClientResponse> filter(ClientRequest request,
ExchangeFunction next) {
LOGGER.info("External Request to {}", request.url()); // (2)
return next.exchange(request); // (3)
}
}
項番 |
説明 |
|---|---|
(1)
|
ExchangeFilterFunctionインタフェースを実装する。 |
(2)
|
非同期リクエストを送信する前に実行する処理を実装する。
上記の実装例では、通信先のURLを出力している。
|
(3)
|
次の
ExchangeFilterFunctionを呼び出す。 |
Bean定義の実装例(WebClientConfig.java)
@Configuration
public class WebClientConfig {
@Bean
public WebClient loggingWebClient() {
// @formatter:off
WebClient webClient = WebClient.builder()
.filter(new LoggingExchangeFilterFunction()) // (1)
.build();
// @formatter:on
return webClient;
}
項番 |
説明 |
|---|---|
(1)
|
ExchangeFilterFunctionの実装クラスをfilterに登録する。Note 複数のFilterを登録したい場合は、
リストに登録された順番に処理されるようになる。 |