5.2. RESTクライアント(HTTPクライアント)¶
5.2.1. Overview¶
5.2.1.1. RESTクライアント実装の種類¶
RestTemplate, RestClient, WebClient, HTTP Service Clientsの4種類の提供を行っている。5.2.1.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の利用を推奨する。RestTemplateを利用したクライアントアプリケーションが、どのようにREST APIを公開しているサーバサイドアプリケーションにアクセスし、RESTによる通信を実現するかを示す。RestTemplate内で利用されるクラスの詳細な説明は、「RESTクライアント実装の構成要素と利用上のポイント」にてまとめて説明を行っているのでこちらを参照されたい。
項番 |
処理 |
説明 |
|---|---|---|
(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)」にて詳しく説明を行うが、 TERASOLUNA Server Framework for Java (5.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.1.2. RestClientを利用したクライアント実装¶
RestClientは、Spring Framework 6.1から導入されているRESTクライアント実装であり、RestTemplateの後継として登場した同期通信をサポートしたRESTクライアント実装である。RestTemplateで説明した通りに、同期通信に関してはRestClientを使うことが推奨されている。RestTemplateで提供されている機能はRestClientでも同等に利用できる。RestClient を利用したクライアントアプリケーションが、どのようにREST APIを公開しているサーバサイドアプリケーションにアクセスし、RESTによる通信を実現するかを示す。RestClient内で利用されるクラスの詳細な説明は、「RESTクライアント実装の構成要素と利用上のポイント」にてまとめて説明を行っているのでこちらを参照されたい。
項番 |
処理 |
説明 |
|---|---|---|
(1)
|
アプリケーションからの呼び出し
|
RestClientのメソッドを実行し、REST API(Web API)の呼び出し依頼を行う。RestClientは、RequestBodyUriSpecとResponseSpecを内部的に保持し、これらを使って流れるようなインターフェース(fluent interface)を実現している。メソッドチェーンで呼び出しを行う際にリクエストの設定(エンドポイント、HTTPメソッド、ヘッダ、ボディ等)を行うエントリーポイントとして
RequestBodyUriSpecを提供し、レスポンスの設定(受信時のデータ型、例外ハンドラ等)を行うエントリーポイントとしてResponseSpecを提供している。なお、
RestClientはインターフェースとなっており、実装クラスとしてはDefaultRestClientが使用される。 |
(2)
|
URL生成
|
UriBuilderFactoryの実装クラスが、UriBuilderの生成を行う。生成した
UriBuilderを使用して、URLのパラメータを置換し送信先のURLの組み立てを行う。なお、
UriBuilderFactoryの実装クラスを指定していない場合は、デフォルト実装クラスであるDefaultUriBuilderFactoryが使用される。 |
(3)
|
リクエスト生成・初期化
|
ClientHttpRequestFactoryの実装クラスがClientHttpRequestの生成を行う。なお、
ClientHttpRequestFactoryの実装クラスを指定していない場合は、使用するHttpClientライブラリによって実装クラスが決定される。ClientHttpRequest生成後、ClientHttpRequestInitializerを設定している場合、ClientHttpRequestInitializerの実装クラスを使用してClientHttpRequest(リクエスト)の初期化処理を行う。「リクエストの生成(ClientHttpRequestFactory)」にて詳しく説明を行うが、 TERASOLUNA Server Framework for Java (5.x) では、
ClientHttpRequestFactoryの実装クラスはHttpComponentsClientHttpRequestFactoryの使用を推奨とする。 |
(4)
|
リクエスト送信前処理
|
HttpMessageConverterの実装クラスを使用して、Javaオブジェクトをリクエストボディに設定する電文(JSON等)に変換する。なお、選択される
HttpMessageConverterは、Javaオブジェクトの型とメディアタイプをもとに決定される。 |
(5)
|
リクエスト送信
|
ClientHttpRequestInterceptorを設定している場合、ClientHttpRequestInterceptorの実装クラスを使用してリクエスト送信前後の共通処理を行う。ClientHttpRequestに電文(JSON等)をリクエストボディに設定して、REST API(Web API)にHTTP経由でリクエストを送信する。REST API(Web API)からレスポンスを受信後、
ClientHttpRequestがClientHttpResponseを生成し、受信したレスポンスデータを設定する。 |
(6)
|
レスポンス取得後処理 / エラーハンドリング
|
レスポンス取得後の後処理として、
ExchangeFunctionが実行される。ExchangeFunctionによりStatusHandlerが実行され、エラー判定及びエラー処理を行う。エラー判定処理後に、
HttpMessageConverterの実装クラスを使用して、レスポンスボディに設定されている電文(JSON等)をJavaオブジェクトに変換する。なお、リクエスト送信前処理と同様で
HttpMessageConverterは、Javaオブジェクトの型とメディアタイプをもとに決定される。 |
(7)
|
アプリケーションへ実行結果返却
|
REST API(Web API)の呼び出し結果(Javaオブジェクト)をアプリケーションへ返却する。
|
5.2.1.1.3. HTTP Service Clientsを利用したクライアント実装¶
HTTP Service Clientsは、Spring Framework 6.0から導入された新しいRESTクライアントの実装であり、インターフェースとアノテーションを活用したシンプルな構成が特徴である。RestTemplate、RestClient、WebClientとは異なり、インターフェースとアノテーションを定義するだけで、REST API(Web API)へのアクセスを簡潔に記述できるように設計されている。@RestControllerによるコントローラ実装と類似しており、直感的で理解しやすい構成である。HTTP Service Clients自体は、RestTemplate、RestClient、WebClientの設定を流用できるため、既存の設定を活かしながら追加で導入するといった移行の容易さも特徴である。HTTP Service Clientsを利用すると便利である。HTTP Service Clientsを利用したクライアントアプリケーションが、どのようにREST APIを公開しているサーバサイドアプリケーションにアクセスし、RESTによる通信を実現するかを示す。HTTP Service Clients内で利用されるクラスの詳細な説明は、「RESTクライアント実装の構成要素と利用上のポイント」にてまとめて説明を行っているのでこちらを参照されたい。
項番 |
処理 |
説明 |
||||||||
|---|---|---|---|---|---|---|---|---|---|---|
(1)
|
アプリケーションからの呼び出し
|
開発者が作成した
@HttpExchangeアノテーションを付与したインターフェースのメソッドを実行し、REST API(Web API)の呼び出し依頼を行う。なお、インターフェースの実装はSpring Frameworkが生成した動的プロキシが使用される。
@HttpExchangeアノテーションは、HTTP Service Clientsであることを表現するためのアノテーションであり、エンドポイント、HTTPメソッド等の情報を定義できる。 |
||||||||
(2)
|
Adapterの実行
|
動的プロキシから
HttpServiceProxyFactoryが呼び出され、実行したインターフェースのメソッドの情報をもとにHttpServiceMethodを取得する。その後、
HttpServiceProxyFactoryからHttpServiceMethodが実行され、リクエスト情報の取得を行う。リクエスト情報取得後、
HttpServiceMethodがHttpExchangeAdapterを実行する。なお、
HttpExchangeAdapterの実装クラスは、RestTemplate、RestClient、WebClientのどの設定を利用するかによって決定される。 |
||||||||
(3)
|
RESTクライアントの実行
|
HttpExchangeAdapterから、各RESTクライアント実装を実行する。
|
||||||||
(4)
|
アプリケーションへ実行結果返却
|
REST API(Web API)の呼び出し結果(Javaオブジェクト)をアプリケーションへ返却する。
|
5.2.1.2. RESTクライアント実装の構成要素と利用上のポイント¶
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定義を行うことで実現できる。RestClient、RestTemplateの両者で UriBuilderFactoryが利用されているため、同様の方法で拡張が可能である。UriBuilderFactoryの具体的なカスタマイズの例は、「URL生成処理をカスタマイズする方法(UriBuilderFactory)」を参照されたい。5.2.1.2.2. リクエストの生成(ClientHttpRequestFactory)¶
RestClientと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の実装クラスについて
ClientHttpRequestFactoryのデフォルト実装クラスは、RestClientとRestTemplateで異なる。RestClientを利用する場合は、クラスパス上に存在するHttpClientライブラリ検出し、優先順位に基づいてClientHttpRequestFactoryの実装が自動的に決定される。RestTemplateを利用する場合は、SimpleClientHttpRequestFactoryが固定で選択される。RestClientではクラスローダーに読み込まれたライブラリをもとに自動的に決定されるため、競合するHttpClientライブラリが依存関係に存在する場合は、意図しない実装クラスが選択される可能性がある。RestTemplateにおいても、デフォルトで設定されるSimpleClientHttpRequestFactoryは、コネクションプールやプロキシ認証等の機能を持たず、他のClientHttpRequestFactoryの実装と比較すると機能が限定されている。ApplicationContextConfig.java
@Bean("restClient")
public RestClient restClient() {
return RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.build();
}
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("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .bufferContent((uri , method) -> true) // (1) .build(); }
項番
説明
(1)RestClient.Builder.bufferContent(BiPredicate)を使用することで、内部的にBufferingClientHttpRequestFactoryが生成され、バッファリングが有効になる。引数のBiPredicateには、URIとHTTPメソッドが引き渡されるため、それらを利用してバッファリングする条件を記載することができる。上記の例では、すべてのリクエストに対してバッファリングを有効にしている。ApplicationContextConfig.java
@Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setBufferingPredicate((uri, httpMethod) -> true); // (1) return restTemplate; }
項番
説明
(1)RestTemplate.setBufferingPredicate(BiPredicate)を使用することで、内部的にBufferingClientHttpRequestFactoryが生成され、バッファリングが有効になる。引数のBiPredicateには、URIとHTTPメソッドが引き渡されるため、それらを利用してバッファリングする条件を記載することができる。上記の例では、すべてのリクエストに対してバッファリングを有効にしている。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)XML Configで定義を記載する場合は、BufferingClientHttpRequestFactoryを明示的にRestTemplateに設定する必要がある。Java Configと異なりバッファリングする条件を記載できないため、すべてのリクエストに対してバッファリングが有効になる。
5.2.1.2.3. リクエスト初期化(ClientHttpRequestInitializer)¶
org.springframework.http.client.ClientHttpRequestInitializerは、Httpリクエスト送信前にClientHttpRequestをカスタマイズするためのインタフェースである。ClientHttpRequestInitializerの他に、 ClientHttpRequestInterceptorも存在するが、それぞれ用途が異なる。両者の違いについては「ClientHttpRequestInterceptorとClientHttpRequestInitializerの使い分けについて」を参照されたい。ClientHttpRequestInitializerはユーザがRestClientやRestTemplateに設定した場合のみ動作し、ClientHttpRequestFactoryによるClientHttpRequestの生成後、ClientHttpRequestInitializerに実装したユーザ定義の初期化処理が実行される。ClientHttpRequestInitializerはデフォルト実装はなく、拡張ポイントとして提供されているインターフェースである。ClientHttpRequestInitializerは@FunctionalInterfaceで提供されているため、RestClientではラムダ式を使用して簡潔に記述することが可能である。ClientHttpRequestInitializerの利用が適しているケースには、リクエストヘッダにカスタムヘッダを一括で設定したい場合などがある。RestClientを利用する場合、このようなケースにはdefaultHeaderメソッドも利用可能である。RestTemplate利用する場合はRestClientのような代替手段が無いため ClientHttpRequestInitializerの利用が適切である。5.2.1.2.4. メッセージ変換(HttpMessageConverter)¶
org.springframework.http.converter.HttpMessageConverterは、アプリケーションで扱うJavaオブジェクトとサーバと通信するための電文(JSON等)を相互に変換するためのインタフェースである。
HttpMessageConverter実装を提供しており、RestClientおよびRestTemplateが使用するHttpMessageConverterは、これらのクライアント生成時に登録され、リクエスト/レスポンスのボディ変換に利用される。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.JacksonJsonHttpMessageConverterによる変換処理を例に説明を行う。5.2.1.2.5. リクエスト前後処理(ClientHttpRequestInterceptor)¶
org.springframework.http.client.ClientHttpRequestInterceptorは、サーバとの通信の前後に共通的な処理を実装するためのインタフェースである。ClientHttpRequestInitializerもあるため、違いについては「ClientHttpRequestInterceptorとClientHttpRequestInitializerの使い分けについて」を参照されたい。ClientHttpRequestInterceptorを使用すると、サーバとの通信時のリクエストやレスポンスのログを出力するといった共通的な処理をRestTemplateや、RestClientに適用することができる。ClientHttpRequestInterceptorを使用した共通処理の実装方法については、「リクエスト送信前後の共通処理の適用(ClientHttpRequestInterceptor)」を参照されたい。ClientHttpRequestInterceptorの動作仕様
ClientHttpRequestInterceptorは複数適用することができ、RestTemplateや、RestClientに登録した順番でチェーン実行される。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. エラーハンドリング(StatusHandler、ResponseErrorHandler)¶
RestClient: 利用可能なエラーハンドラの実装としてStatusHandlerを使用した実装と、ResponseErrorHandlerを使用した実装の2つが存在する。デフォルトでは、StatusHandlerが利用される。RestTemplate:利用可能なエラーハンドラ実装はResponseErrorHandlerを使用したもののみ。デフォルトでは、DefaultResponseErrorHandlerが利用される。
StatusHandler とDefaultResponseErrorHandlerで同様である。項番 |
HTTPステータスコード |
動作仕様 |
|---|---|---|
(1)
|
2xx(正常系)
|
エラー処理は行わない。
|
(2)
|
4xx(クライアントエラー系)
|
org.springframework.web.client.HttpClientErrorExceptionを発生させる。 |
(3)
|
5xx(サーバエラー系)
|
org.springframework.web.client.HttpServerErrorExceptionを発生させる。 |
StatusHandlerかResponseErrorHandlerの実装を作成して、各RESTクライアントに設定する必要がある。5.2.1.2.8. レスポンス取得後処理(ExchangeFunction、ResponseExtractor)¶
ExchangeFunctionとResponseExtractorを提供している。
ExchangeFunction ( RestClient ) の場合
org.springframework.web.client.ExchangeFunctionを使用して後処理を実装する。デフォルトで利用される実装が提供されており、org.springframework.web.client.DefaultRestClient内で定義されているExchangeFunctionの関数実装が利用される。動作としては、DefaultRestClientがレスポンスを受信した後に実行され、後処理としてHTTPステータスのエラー判定処理、メッセージの変換処理を行う。RestTemplateとの違いは、HTTPステータスのエラー判定処理をレスポンス受信後処理(ExchangeFunction) で実装する必要がある点にある。
ResponseExtractor ( RestTemplate ) の場合
org.springframework.web.client.ResponseExtractorを使用して後処理を実装する。デフォルトで利用される実装が提供されており、org.springframework.web.client.HttpMessageConverterExtractorが利用される。動作としては、RestTemplateがレスポンスを受信し、HTTPステータスのエラー判定処理を行った後に実行され、後処理としてメッセージの変換処理を行う。
受信したレスポンスを操作する必要がある場合などは ExchangeFunctionやResponseExtractorをカスタマイズする。実際の方法は、「ファイルダウンロード」で説明しているので、必要に応じてこちらも参照されたい。
5.2.2. How to use(同期通信)¶
RestClientとRestTemplateを使用した、同期通信を行うRESTクライアントの実装方法について説明する。RestClientを用いた実装におけるBean定義例では、Spring FrameworkにおいてBuilderパターンを利用したメソッドチェーンによる実装を前提としているため、Java Configを利用した設定例のみを示す。5.2.2.1. 同期通信のセットアップ¶
同期通信を行う場合は、RestClientまたはRestTemplateをDIコンテナに登録し、RESTクライアントを利用するコンポーネントにインジェクションする。
5.2.2.1.1. 依存ライブラリ設定¶
RestClient / 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定義¶
RestClient / RestTemplateのBean定義を行い、DIコンテナに登録する。これらのBeanは、クライアントアプリケーションにおける、リクエスト送信の起点となる。RestClient / RestTemplateのBean定義時にはClientHttpRequestFactoryを通して実際のリクエスト作成で利用する実装を選択することができる。ClientHttpRequestFactory実装であるHttpComponentsClientHttpRequestFactoryの利用を前提とする。HttpComponentsClientHttpRequestFactoryは、Spring Framework 6.1よりサーバへのリクエスト送信を行う前にリクエストボディをバッファリングしないようになったため、リクエストのContent-Lengthヘッダが設定されなくなった。Bean定義ファイルの定義例
ApplicationContextConfig.java
@Bean("restClient")
public RestClient restClient() {
return RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory()) // (1)
.build(); // (2)
}
項番 |
説明 |
|---|---|
(1)
|
RestClient.BuilderのrequestFactoryメソッドに、org.springframework.http.client.HttpComponentsClientHttpRequestFactoryを設定する。HttpComponentsClientHttpRequestFactoryを利用した通信設定については「リクエスト生成処理のカスタマイズ方法(ClientHttpRequestFactory)」を参照されたい。 |
(2)
|
RestClient.Builderのbuildメソッドを使用してRestClientのBeanを生成し、DIコンテナに登録する。 |
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)」を参照されたい。 |
代表的なRestClient / RestTemplateのBeanのカスタマイズ方法は、下記で実装例を示しているため、必要に応じて参照されたい。
5.2.2.1.3. RESTクライアントの利用¶
RESTクライアントを利用する場合は、DIコンテナに登録されているRestClient / RestTemplateをインジェクションする。
RESTクライアントのインジェクション例
@Service
public class AccountServiceImpl implements AccountService {
@Inject
RestClient restClient;
// omitted
}
@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の設定方法は、利用するRESTクライアント実装によって異なる。
RestClient:RestClient.builder().baseUrl(...)メソッドを使用して設定する。RestTemplate:DefaultUriBuilderFactoryにbaseUrlを設定して、RestTemplateに適用する必要がある。
ApplicationContextConfig.java
@Value("${api.baseUrl:http://localhost:8080/api/v1}") // (1)
private String url;
@Bean("restClient")
public RestClient restClient() {
return RestClient.builder().baseUrl(url) // (2)
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.build();
}
項番 |
説明 |
|---|---|
(1)
|
プロパティファイルから基準となるURLを取得し設定を行う。
|
(2)
|
RestClient.BuilderのbaseUrlメソッドを使用して、(1)のURLを設定する。 |
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 RestClient restClient;
public User getUser(String id) {
return restClient.get().uri(uri, id)
.retrieve()
.body(User.class);
}
項番 |
説明 |
|---|---|
(1)
|
baseUrlで指定した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 RestClient restClient;
public User getUser(String id) {
return restClient.get().uri(uri, id)
.retrieve()
.body(User.class);
}
項番 |
説明 |
|---|---|
(1)
|
プロパティファイルから絶対パスで記載されたURLを取得し、利用するURLとして指定する。
|
@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リクエスト送信)¶
RestClient / RestTemplateは、GETリクエスト送信を行うためのメソッドを複数提供している。代表的なメソッドを以下に示す。項番 |
用途 |
RestClientのメソッド |
RestTemplateのメソッド |
|---|---|---|---|
(1)
|
レスポンスボディを任意のデータ型で取得する
|
RestClient.get().uri(...).retrieve().body(...) |
RestTemplate.getForObject(...) |
(2)
|
レスポンス情報を含む結果を取得する
|
RestClient.get().uri(...).retrieve().toEntity(...) |
RestTemplate.getForEntity(...) |
5.2.2.3.1. GETメソッドを指定したリクエスト送信の実装¶
Userを表現するJavaBeanクラスにてレスポンスボディを受け取る実装である。bodyメソッドの使用例
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
private String uri;
メソッド内部
User user = restClient.get() // (1)
.uri(uri) // (2)
.retrieve() // (3)
.body(User.class); // (4)
項番 |
説明 |
|---|---|
(1)
|
getメソッドを使用してHTTPメソッドにGETを設定し、RequestHeadersUriSpecを取得する。 |
(2)
|
RequestHeadersUriSpec.uriメソッドを使用して、リクエスト送信先のURLを設定する。他にもリクエストに対する設定を行う必要がある場合は、
RequestHeadersUriSpecを利用して、リクエストの設定を行う。 |
(3)
|
RequestHeadersUriSpecでリクエストに対する設定を行った後、retrieveメソッドを呼び出し、レスポンスの設定(受信時のデータ型、例外ハンドラ等)を行うためのResponseSpecを取得する。retrieveメソッドでは、ResponseSpecの生成、取得までを行い、リクエストの送信は行われない。リクエストの送信は、
ResponseSpecのメソッド(body、toEntity等)が呼び出されたタイミングで行われる。 |
(4)
|
ResponseSpec.bodyメソッドにてレスポンスボディを受け取る型を指定し、リクエストの送信を行う。レスポンスボディのデータは
HttpMessageConverterによってbodyの引数に指定したJavaクラスへ変換された後、返却される。 |
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. ステータスコードやレスポンスヘッダを含めたレスポンスの取得¶
toEntityメソッドの使用例
インポート宣言
import org.springframework.http.ResponseEntity;
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
private String uri;
メソッド内部
ResponseEntity<User> responseEntity =
restClient.get().uri(uri) // (1)
.retrieve()
.toEntity(User.class); // (2)
HttpStatusCode statusCode = responseEntity.getStatusCode(); // (3)
HttpHeaders header = responseEntity.getHeaders(); // (4)
User user = responseEntity.getBody(); // (5)
項番 |
説明 |
|---|---|
(1)
|
RestClient.getメソッドを使用してHTTPメソッドにGETを設定する。 |
(2)
|
ResponseSpec.toEntityメソッドを使用した場合は、戻り値はorg.springframework.http.ResponseEntityとなる。 |
(3)
|
HTTPステータスコードは
ResponseEntityのgetStatusCodeメソッドを用いて取得する。 |
(4)
|
レスポンスヘッダは
ResponseEntityのgetHeadersメソッドを用いて取得する。 |
(5)
|
レスポンスボディは
ResponseEntityのgetBodyメソッドを用いて取得する。 |
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等)がコレクション形式の場合は、以下の実装を行う。
コレクション形式のデータの取得例
List<User> users = //(1)
restClient.get().uri(uri)
.retrieve()
.body(new ParameterizedTypeReference<List<User>>(){}); //(2)
項番 |
説明 |
|---|---|
(1)
|
List<レスポンスデータの型>を指定する。 |
(2)
|
ResponseSpec.bodyメソッドの引数にorg.springframework.core.ParameterizedTypeReferenceのインスタンスを指定し、型パラメータにList<レスポンスデータの型>を指定する。 |
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リクエスト送信)¶
RestClient / RestTemplateは、POSTリクエスト送信を行うためのメソッドを複数提供している。代表的なメソッドの一覧を以下に示す。項番 |
用途 |
RestClientのメソッド |
RestTemplateのメソッド |
|---|---|---|---|
(1)
|
レスポンスボディを任意のデータ型で取得する
|
RestClient.post().uri(...).body(...).retrieve().body(...) |
RestTemplate.postForObject(...) |
(2)
|
レスポンス情報を含む結果を取得する
|
RestClient.post().uri(...).body(...).retrieve().toEntity(...) |
RestTemplate.postForEntity(...) |
5.2.2.4.1. POSTメソッドを指定したリクエスト送信の実装¶
Userを表現するJavaBeanクラスにてレスポンスボディを受け取る実装である。bodyメソッドの使用例
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
private String uri;
メソッド内部
var user = new User();
// omitted
User user = restClient.post() // (1)
.uri(uri) // (2)
.body(user) // (3)
.retrieve() // (4)
.body(User.class); // (5)
項番 |
説明 |
|---|---|
(1)
|
postメソッドを使用してHTTPメソッドにPOSTを設定し、RequestBodyUriSpecを取得する。 |
(2)
|
RequestBodyUriSpec.uriメソッドを使用して、リクエスト送信先のURLを設定する。他にもリクエストに対する設定を行う必要がある場合は、
RequestBodyUriSpecを利用して、リクエストの設定を行う。 |
(3)
|
RequestBodyUriSpec.bodyメソッドを使用してリクエストボディを設定する。設定されたリクエストボディのオブジェクトは、
HttpMessageConverterによって電文形式(JSON等)に変換される。 |
(4)
|
RequestBodyUriSpecでリクエストに対する設定を行った後、retrieveメソッドを呼び出し、レスポンスの設定(受信時のデータ型、例外ハンドラ等)を行うためのResponseSpecを取得する。retrieveメソッドでは、ResponseSpecの生成、取得までを行い、リクエストの送信は行われない。リクエストの送信は、
ResponseSpecのメソッド(body、toEntity等)が呼び出されたタイミングで行われる。 |
(5)
|
ResponseSpec.bodyメソッドにてレスポンスボディを受け取る型を指定し、リクエストの送信を行う。レスポンスボディのデータは
HttpMessageConverterによってbodyの引数に指定したJavaクラスへ変換された後、返却される。 |
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 = restClient.get().uri(uri)
.retrieve()
.body(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をキャッチしてリトライ処理を行っている。 |
(2)
|
必要に応じて例外オブジェクトからレスポンスデータを取得し、ログ出力や処理の判定等に利用する。
エラー時のレスポンスデータ(HTTPステータスコード、レスポンスヘッダ、レスポンスボディなど)は、例外クラスのgetterメソッドを呼び出すことで取得することができる。
取得可能なレスポンスデータについては、RestClientResponseExceptionを参照されたい。
|
フィールド宣言部
@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ステータスコードでハンドリング(エラーハンドラの拡張)¶
RestClientでは、StatusHandlerと、ResponseErrorHandlerインタフェースの実装クラスを使った2つのエラーハンドリングの実装方法があるので、どちらかを拡張しRestClientに設定することで、独自のエラー処理を行うことができる。RestTemplateでは、ResponseErrorHandlerインタフェースの実装クラスをRestTemplateに設定することで、独自のエラー処理を行うことができる。ResponseEntityを返却し、HTTPステータスコードでエラーハンドリングが行えるように拡張をしている。Bean定義ファイルの定義例(StatusHandler)
StatusHandlerを利用した実装例である。ApplicationContextConfig.java
@Bean("restClient")
public RestClient restClient() {
return RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> {}) // (1)
.build();
}
項番 |
説明 |
|---|---|
(1)
|
RestClient.BuilderのdefaultStatusHandlerメソッドにPredicate<HttpStatusCode>と、ResponseSpec.ErrorHandlerを設定する。上記の実装例では、
HttpStatusCodeがサーバエラー(5xx系)及びクライアントエラー(4xx系)の場合、例外を発生させずに処理を継続するようにしている。HttpStatusCodeで利用できるメソッドについては、「HttpStatusCodeのJavadoc」を参照されたい。なお、上記の実装は処理を継続し正常終了扱いとしているため、デフォルトの動作と異なり後続のメッセージ変換処理(
HttpMessageConverter) は動作する。そのため、サーバ側から返却するレスポンスのBody部は、サーバエラー(5xx系)及びクライアントエラー(4xx系)が発生した際も
HttpMessageConverterで型変換できるような値を返却する必要がある。 |
クライアント処理の実装例
int retryCount = 0;
while (true) {
responseEntity = restClient.get().uri(uri)
.retrieve()
.toEntity(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ステータスコードを取得して、その値に応じて処理を制御することができる。 |
エラーハンドラの実装クラスの作成例
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.5.3. RestClient呼び出し毎のエラーハンドリング(エラーハンドラの拡張)¶
RestClientのみの実装となるが、共通のエラーハンドラとは別にRestClientの呼び出し毎にエラーハンドラの拡張が行える。RestClientのonStatusメソッドを使用して、RestClientの呼び出し毎でエラーハンドリングを行う方法について説明する。RestClient呼び出し毎のエラーハンドリングの実装例
User user = restClient.get().uri(uri)
.retrieve()
.onStatus(HttpStatusCode::is5xxServerError, (request, response) -> { // (1)
// omitted
})
.body(User.class);
項番 |
説明 |
|---|---|
(1)
|
上記の実装例では、サーバエラー(5xx系)のHTTPステータスコードが返却された場合のエラーハンドリングを行っている。
HttpStatusCodeで利用できるメソッドについては、「HttpStatusCodeのJavadoc」を参照されたい。なお、
onStatusメソッドの記載がある場合は、defaultStatusHandlerの実装より優先されて処理される。上記の実装例の動作としては、サーバエラー(5xx系)のHTTPステータスコードが返却された場合にのみ適用され、クライアントエラー(4xx系)のHTTPステータスコードが返却された場合は
defaultStatusHandlerの実装が適用される。 |
5.2.2.6. リクエストヘッダの設定¶
5.2.2.6.1. アプリケーション全体で共通のリクエストヘッダを設定する方法¶
RestClientではdefaultHeader、RestTemplateではClientHttpRequestInitializerを使用することで共通のリクエストヘッダの設定が可能である。Bean定義ファイルの定義例
ApplicationContextConfig.java
@Bean("restClient")
public RestClient restClient() {
return RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.defaultHeader("Custom-Header", "CustomHeaderValue") // (1)
.build();
}
項番 |
説明 |
|---|---|
(1)
|
RestClient.BuilderのdefaultHeaderメソッドを使用してリクエストヘッダにカスタムヘッダを付与している。上記の例では、
Custom-HeaderというキーでCustomHeaderValueという値を設定している。複数のリクエストヘッダを設定する場合は、
RestClient.BuilderのdefaultHeadersメソッドを使用して実装することができる。 |
Note
ClientHttpRequestInitializerを使った実装
RestClientの場合も、以下のようにrequestInitializerメソッドを使ってClientHttpRequestInitializerを使った実装も可能である。RestClientを利用する場合、リクエストヘッダの設定には、ClientHttpRequestInitializerを利用する事もできるが、リクエストヘッダのみを設定したい際にはdefaultHeaderメソッドの利用を推奨する。defaultHeaderメソッドを利用するほうが、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. リクエスト送信単位でリクエストヘッダを設定する方法¶
RestClientとRestTemplateで異なる。RestClientで実装する場合、Content-TypeやAcceptといった代表的なHTTPヘッダについては、RequestBodyUriSpecに専用の設定メソッド(contentType、accept)が用意されているため、これを利用すると良い。RequestBodyUriSpecのheaderメソッドを使用して設定することができる。RestTemplateで実装する場合は、RequestEntityのメソッドを使用してHTTPヘッダを設定する。RestClientと同様に代表的なHTTPヘッダ用の設定メソッドがあるため、Content-TypeやAcceptといったヘッダの設定にはこちらを用いるのが良い。RequestEntity.HeadersBuilderのheaderメソッドを使用して設定する。5.2.2.6.2.1. Content-Typeヘッダの設定¶
Content-Typeヘッダの設定例
インポート宣言
import org.springframework.http.ResponseEntity;
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
private String uri;
メソッド内部
var user = new User();
// omitted
ResponseEntity<User> responseEntity = restClient.post().uri(uri)
.contentType(MediaType.APPLICATION_JSON) // (1)
.body(user)
.retrieve()
.toEntity(User.class);
User user = responseEntity.getBody();
項番 |
説明 |
|---|---|
(1)
|
RequestBodyUriSpec.contentTypeメソッドを使用し、Content-Typeヘッダの値を指定する。上記の実装例では、送信時のデータ形式がJSONであることを示す「
application/json」を設定している。 |
インポート宣言
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ヘッダの設定例
メソッド内部
ResponseEntity<User> responseEntity = restClient.get().uri(uri)
.accept(MediaType.APPLICATION_JSON) // (1)
.retrieve()
.toEntity(User.class);
User user = responseEntity.getBody();
項番 |
説明 |
|---|---|
(1)
|
RequestBodyUriSpec.acceptメソッドを使用して、Acceptヘッダの値を設定する。上記の実装例では、取得可能なデータ形式がJSONであることを示す「
application/json」を設定している。 |
メソッド内部
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. 任意のリクエストヘッダの設定¶
カスタムヘッダの実装例
メソッド内部
ResponseEntity<User> responseEntity = restClient.get().uri(uri)
.header("Custom-Header", "CustomValue") // (1)
.retrieve()
.toEntity(User.class);
User user = responseEntity.getBody();
項番 |
説明 |
|---|---|
(1)
|
RequestBodyUriSpec.headerメソッドを使用してリクエストヘッダの名前と値を設定する。 |
メソッド内部
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を使用した定義例である。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("timeoutRestClient")
public RestClient timeoutRestClient() {
return RestClient.builder()
.requestFactory(clientHttpRequestFactory()) // (8)
.build();
}
項番 |
説明 |
|---|---|
(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)
|
RestClient.BuilderのrequestFactoryメソッドを使用して生成したHttpComponentsClientHttpRequestFactoryを設定する。HttpComponentsClientHttpRequestFactoryで設定したタイムアウト値を超えた場合はorg.springframework.web.client.ResourceAccessExceptionが発生する。 |
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)
restClient.post().uri(uri)
.contentType(MediaType.MULTIPART_FORM_DATA) //(3)
.body(multiPartBody) //(4)
.retrieve()
.body(Void.class);
項番 |
説明 |
|---|---|
(1)
|
マルチパートリクエストとして送信するデータを格納するために
MultiValueMapを生成する。 |
(2)
|
パラメータ名をキーに指定して、アップロードするファイルを
MultiValueMapに追加する。上記例では、
fileというパラメータ名を指定して、クラスパス上に配置されているファイルをアップロードファイルとして追加している。 |
(3)
|
Content-Typeヘッダのメディアタイプを
multipart/form-dataに設定する。MultiValueMapにString値以外が設定されている場合、Content-TypeヘッダはFormHttpMessageConverterによってmultipart/form-dataが設定される。したがって、上記の例では省略しても問題はない。
|
(4)
|
アップロードするファイルが格納されている
MultiValueMapをリクエストボディに設定する。 |
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で読み込み、読み込んだ内容をファイルに書き出す実装例を示す。ExchangeFunctionやResponseExtractorを使用したダウンロードの実装例である。インポート宣言
import org.springframework.util.FileCopyUtils;
メソッド内部
File rcvFile =
restClient.get().uri(uri).exchange((request, response) -> { // (1)
// (2)
if (response.getStatusCode().isError()) {
throw response.createException();
}
File rcvFile = File.createTempFile("rcvFile", "zip"); // (3)
FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile)); // (4)
return rcvFile; // (5)
}); // (6)
// omitted
項番 |
説明 |
|---|---|
(1)
|
RequestBodyUriSpec.exchangeメソッドを利用し、レスポンス取得後処理を実装する。引数には
ExchangeFunctionが定義されているため、関数にレスポンス取得後処理を実装する。 |
(2)
|
エラー判定及びエラー処理を実装する。
ExchangeFunctionの引数として定義されているConvertibleClientHttpResponseからステータスコードを取得し、クライアントエラー及びサーバエラーの場合、ConvertibleClientHttpResponseのcreateExceptionメソッドを使用して例外をスローする。ConvertibleClientHttpResponseのcreateExceptionメソッドを使用することにより、StatusHandlerで生成される例外をスローすることができる。「レスポンス取得後処理(ExchangeFunction、ResponseExtractor)」にて説明している通り、
ExchangeFunctionはエラー判定、エラー処理、レスポンスボディの変換処理を行う必要があるため、RestTemplateとは異なり、エラー判定及びエラー処理も実装する必要がある。 |
(3)
|
出力するファイルを生成する。
|
(4)
|
レスポンスボディを
InputStreamで読み込み、FileCopyUtilsを使用して、(3)で生成したファイルにレスポンスボディを少しずつ書き出す。 |
(5)
|
ファイル作成後、作成したファイルを返却する。
Note HTTPヘッダや、ステータスコードも返却する場合 HTTPヘッダや、ステータスコードも含めて返却したい場合は、
|
(6)
|
RestClientを実行してファイルのダウンロードを行い、出力したファイルを取得する。 |
インポート宣言
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 = restClient.get().uri(uri)
.retrieve()
.body(byte[].class); //(1)
項番 |
説明 |
|---|---|
(1)
|
ダウンロードファイルを指定したデータ型で扱う。
上記の例では、ダウンロードしたファイルをバイト配列で取得する。
|
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("restClient")
public RestClient restClient() {
return RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.uriBuilderFactory(customUriBuilderFactory()) // (2)
.build();
項番 |
説明 |
|---|---|
(1)
|
CustomUriBuilderFactoryのBeanを定義する。 |
(2)
|
RestClient.BuilderのuriBuilderFactoryメソッドに、CustomUriBuilderFactoryの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の実装例
RestClient / 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を、RestClient / RestTemplateに設定する。
ApplicationContextConfig.java
@Value("${rscl.keystore.filename}")
private String keystoreFilename;
@Value("${rscl.keystore.password}")
private String keystorePassword;
// omitted
@Bean("httpsRestClient")
public RestClient httpsRestClient() throws Exception {
return RestClient.builder()
.requestFactory(httpsRequestFactoryBean().getObject()) // (1)
.build();
}
// (1)
@Bean("httpsRequestFactoryBean")
public RequestFactoryBean httpsRequestFactoryBean() {
var factory = new RequestFactoryBean();
factory.setKeyStoreFileName(keystoreFilename);
factory.setKeyStorePassword(keystorePassword.toCharArray());
return factory;
}
項番 |
説明 |
|---|---|
(1)
|
作成した
RequestFactoryBeanをRestClient.BuilderのrequestFactoryメソッドに指定する。RequestFactoryBeanには、キーストアファイルのファイル名とパスワードを渡す。 |
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クライアントの使用方法
RestClient / RestTemplateの使い方については、SSL自己署名証明書を使用しない場合と同様であるため、以下を参照されたい。
5.2.3.2.2. HTTP Proxyサーバの設定方法¶
RestClient / RestTemplateのBean定義にてHTTP Proxyサーバの設定が必要である。RestClient / 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("proxyRestClient")
public RestClient proxyRestClient() {
return RestClient.builder()
.requestFactory(httpComponentsClientHttpRequestFactory()) // (9)
.build();
}
項番 |
説明 |
|---|---|
(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)
|
RestClientのBean定義を行う。 |
(9)
|
RestClient.BuilderのrequestFactoryメソッドを使用して、(6)で生成したHttpComponentsClientHttpRequestFactoryを設定する。 |
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を設定する。
|
(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("proxyRestClient")
public RestClient proxyRestClient() {
return RestClient.builder()
.requestFactory(httpComponentsClientHttpRequestFactory())
.build();
}
項番 |
説明 |
|---|---|
(1)
|
HttpClientBuilderのsetDefaultCredentialsProviderメソッドに、BasicCredentialsProviderを設定する。BasicCredentialsProviderは、FactoryBeanを実装したBasicCredentialsProviderFactoryBeanを使用し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("restClient")
public RestClient restClient() {
return RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.configureMessageConverters(f -> (2)
f.registerDefaults() (3)
.addCustomConverter(customHttpMessageConverter()) (4)
)
.build();
}
項番 |
説明 |
|---|---|
(1)
|
カスタムメッセージコンバータのBeanを定義する。
|
(2)
|
RestClient.BuilderのconfigureMessageConvertersメソッドにてHttpMessageConverterを登録する。 |
(3)
|
関数の引数に
HttpMessageConverters.ClientBuilderが渡されるため、ClientBuilderのregisterDefaultsメソッドを呼び出し、デフォルトのHttpMessageConverterの登録を行う。 |
(4)
|
ClientBuilderのaddCustomConverterメソッドにて(1)で定義したBeanを登録する。なお、
ClientBuilderにはカスタムメッセージコンバータの追加以外にも、各種メディアタイプに対応するHttpMessageConverterの置き換え用のメソッド(with~)が用意されている。詳しくは、HttpMessageConverters.BuilderのJavadocを参照されたい。
これらの置き換え用のメソッドを使用することで、既存のメディアタイプに対応する
HttpMessageConverterの変更も可能である。 |
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. リクエスト送信前後のロギング処理¶
サーバとの通信ログを出力したい場合は、以下のような実装を行う。
通信ログ出力の実装例
Bean定義ファイルの定義例
ApplicationContextConfig.java
@Bean("restClient")
public RestClient restClient() {
return RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.requestInterceptor((request, body, execution) -> { // (1)
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)
})
.build();
}
項番 |
説明 |
|---|---|
(1)
|
RestClient.BuilderのrequestInterceptorメソッドにリクエスト送信前後の処理を実装する。 |
(2)
|
リクエスト送信前の共通処理を実装する。
上記の実装例では、リクエストヘッダとリクエストボディの内容をログに出力している。
|
(3)
|
関数の引数として受け取った
org.springframework.http.client.ClientHttpRequestExecutionのexecuteメソッドを実行し、リクエストの送信を行う。 |
(4)
|
レスポンス受信後の共通処理を実装する。
上記の実装例では、レスポンスヘッダとステータスコードの内容をログに出力している。
|
(5)
|
(3)で受信したレスポンスを返却する。
|
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("restClient")
public RestClient restClient() {
return RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.requestInterceptors(interceptors -> { // (1)
interceptors.add((request, body, execution) -> { // (2)
if (logger.isInfoEnabled()) {
var requestBody = new String(body, StandardCharsets.UTF_8);
logger.info("Request Header {}", request.getHeaders());
logger.info("Request Body {}", requestBody);
}
ClientHttpResponse response = execution.execute(request, body);
if (logger.isInfoEnabled()) {
logger.info("Response Header {}", response.getHeaders());
logger.info("Response Status Code {}", response.getStatusCode());
}
return response;
});
interceptors.add((request, body, execution) -> { // (3)
// omitted
return execution.execute(request, body);
});
})
.build();
}
項番 |
説明 |
|---|---|
(1)
|
RestClient.BuilderのrequestInterceptorsメソッドの引数にはConsumer<List<ClientHttpRequestInterceptor>>が設定されているため、関数を呼び出しClientHttpRequestInterceptorを追加する。Listに追加した順番でClientHttpRequestInterceptorのチェーンが実行される。上記の例では、リクエスト送信前は(2)、(3)の順に実行され、レスポンス受信後は(3)、(2)の順に実行される。
|
(2)
|
(1)で取得した
List<ClientHttpRequestInterceptor>にClientHttpRequestInterceptorを追加する。 |
(3)
|
(2)と同様に、登録したい
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. How to use(HTTP Service Clients)¶
HTTP Service Clientsを使用したクライアント処理の実装方法について説明する。HTTP Service Clientsの設定方法は、@HttpExchangeアノテーションを付与したインターフェースを、インターフェース単位で設定・登録する方法と、インターフェースをグループ単位で設定し一括登録する方法の2通りがある。HttpServiceProxyFactoryにクライアント設定を行ったうえで、@HttpExchangeを付与したインターフェースのクライアント実装(動的プロキシ)を作成し、DIコンテナに登録を行う。HttpServiceGroupConfigurerにてグループ単位でクライアント設定を行う。@ImportHttpServicesにグループに含める@HttpExchangeを付与したインターフェースを指定し、指定したインターフェースのクライアント実装(動的プロキシ)を一括作成して、DIコンテナに登録を行う。@HttpExchangeアノテーションを付与したインターフェースの作成粒度に制約はないため、一つのインターフェースにてサーバへのアクセスを全て集約して定義することも可能ではあるが、可読性やメンテナンス性を考慮すると意味のある単位で分割して定義を行う方が望ましい。@HttpExchangeアノテーションを付与したインターフェースは、リソース単位で作成することを推奨し、以降の説明においてもリソース単位で作成することを前提として説明を行う。インターフェース単位で設定・登録する場合(HttpServiceProxyFactoryを利用した設定)
アクセスするリソース単位で繰り返しBean定義を行う必要があるため、小規模なシステムでHTTP Service Clientsを数個程度利用する場合に適している。また、クライアントの設定をカスタマイズ(ClientHttpRequestFactory、ClientHttpRequestInterceptorの設定等)する場合、HTTP Service Clientsに直接設定するのではなく、元となる各RESTクライアント実装に設定を行う必要があるため、既にRestClientやRestTemplateを利用していてHTTP Service Clientsに移行したい場合に適している。グループ単位で設定を集約して一括登録する場合(@ImportHttpServicesアノテーションとHttpServiceGroupConfigurerを利用した設定)
一括でまとめてBean定義を行うことができるため、中規模以上のシステムでHTTP Service Clientsを多数利用する場合に適している。クライアントの設定をカスタマイズする際には、RestClientやRestTemplateで利用する拡張ポイントがRestClientHttpServiceGroupConfigurer経由で同じように利用できるため、HTTP Service Clientsだけで設定を完結できる。これにより設定内容をHTTP Service Clientsに集約できるため、新規でRESTクライアントを実装する場合に適している。
5.2.4.1. HTTP Service Clients(インターフェース単位で設定・登録する場合)のセットアップ¶
HttpServiceProxyFactoryを利用しインターフェース単位で設定・登録する場合は、以下の手順でセットアップを行う。
RestClient、RestTemplateのいずれかのBeanをDIコンテナに登録する。@HttpExchangeアノテーションを付与したインターフェースを作成する。- DIコンテナに登録したRESTクライアントのBeanを、
Adapterを介してHttpServiceProxyFactoryに設定する。HttpServiceProxyFactoryを使用して@HttpExchangeアノテーションを付与したインターフェースの動的プロキシを生成し、DIコンテナに登録する。 - RESTクライアントを利用するコンポーネントにて、
@HttpExchangeアノテーションを付与したインターフェースの動的プロキシをインジェクションする。
5.2.4.1.1. RESTクライアントのBean定義(インターフェース単位で設定・登録する場合)¶
RestClient、RestTemplateのいずれかのBeanをDIコンテナに登録する。HttpServiceProxyFactoryを利用する場合は、各RESTクライアント実装の設定を引き継ぐためHTTP Service Clientsで設定できる内容は限られている。ClientHttpRequestFactoryやClientHttpRequestInterceptor等の拡張ポイントは設定できない。RestClient、RestTemplateにて説明を行っているので、以下を参照されたい。5.2.4.1.2. HTTP Service Clientsの定義(インターフェース単位で設定・登録する場合)¶
@HttpExchangeアノテーションを付与したインターフェースを作成する。RestClient、RestTemplateのいずれの場合でも同様である。import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
@HttpExchange(url = "/api/v1") // (1)
public interface UserApi { // (2)
@GetExchange(value = "users/{id}")
User getUser(@PathVariable("id") String id);
}
項番 |
説明 |
|---|---|
(1)
|
REST APIのURLとマッピングするアノテーションとして
@HttpExchangeアノテーションを定義する。url属性には、本インターフェースの基準となるURLを指定する。
上記の例では、「基準となるURLを設定する」を設定したうえで、
/api/v1をこのインターフェースで利用する共通のURLとして設定している。なお、
@HttpExchangeアノテーションはクラスで指定をせず、メソッドに付与することも可能である。 |
(2)
|
インターフェースとして定義を行う。
実装は動的プロキシとして作成されるため、実装クラスは不要である。
|
5.2.4.1.3. HTTP Service ClientsのBean定義(インターフェース単位で設定・登録する場合)¶
RestClient、RestTemplateのBeanを、Adapterを介してHTTP Service Clientsに設定する。ApplicationContextConfig.java
@Value("${api.baseUrl:http://localhost:8080}")
private String url;
@Bean("restClient")
public RestClient restClient() {
return RestClient.builder()
.baseUrl(url)
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.build();
}
// (1)
@Bean("userApi")
public UserApi userApi() {
RestClientAdapter adapter = RestClientAdapter.create(restClient()); // (2)
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); // (3)
return factory.createClient(UserApi.class); // (4)
}
項番 |
説明 |
|---|---|
(1)
|
HTTP Service ClientsのBean定義を行う。返却値として「HTTP Service Clientsの定義(インターフェース単位で設定・登録する場合)」で定義したインターフェースを設定する。
|
(2)
|
org.springframework.web.client.support.RestClientAdapterのcreateメソッドを使用してRestClientAdapterのインスタンスを生成する。引数には、
RestClientのBeanを指定する。 |
(3)
|
org.springframework.web.service.invoker.HttpServiceProxyFactoryのbuilderForメソッドを使用して、(2)で生成したAdapterを設定する。その後、
buildメソッドを実行してHttpServiceProxyFactoryのインスタンスを生成する。 |
(4)
|
HttpServiceProxyFactoryの createClientメソッドを使用して、(1)で定義したインターフェースの型(クラス)を設定する。返却値として
HTTP Service ClientsのBean(動的プロキシ)を返却し、DIコンテナに登録する。 |
ApplicationContextConfig.java
@Value("${api.baseUrl:http://localhost:8080}")
private String url;
@Bean("restTemplate")
public RestTemplate restTemplate() {
var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(url));
return restTemplate;
}
// (1)
@Bean("userApi")
public UserApi userApi() {
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate()); // (2)
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); // (3)
return factory.createClient(UserApi.class); // (4)
}
項番 |
説明 |
|---|---|
(1)
|
HTTP Service ClientsのBean定義を行う。返却値として「HTTP Service Clientsの定義(インターフェース単位で設定・登録する場合)」で定義したインターフェースを設定する。
|
(2)
|
org.springframework.web.client.support.RestTemplateAdapterのcreateメソッドを使用してRestTemplateAdapterのインスタンスを生成する。引数には、
RestTemplateのBeanを指定する。 |
(3)
|
org.springframework.web.service.invoker.HttpServiceProxyFactoryのbuilderForメソッドを使用して、(2)で生成したAdapterを設定する。その後、
buildメソッドを実行してHttpServiceProxyFactoryのインスタンスを生成する。 |
(4)
|
HttpServiceProxyFactoryの createClientメソッドを使用して、(1)で定義したインターフェースの型(クラス)を設定する。返却値として
HTTP Service ClientsのBean(動的プロキシ)を返却し、DIコンテナに登録する。 |
5.2.4.1.4. HTTP Service Clientsの利用(インターフェース単位で設定・登録する場合)¶
HTTP Service ClientsのBeanをインジェクションする。@Service
public class AccountServiceImpl implements AccountService {
@Inject
private UserApi userApi; // (1)
@Override
public User getAccount(String id) {
return userApi.getUser(id); // (2)
}
// omitted
}
項番 |
説明 |
|---|---|
(1)
|
@Injectアノテーションを付与し、HTTP Service ClientsのBeanをインジェクションする。 |
(2)
|
「HTTP Service Clientsの定義(インターフェース単位で設定・登録する場合)」で定義したメソッドを呼び出し、処理を実行する。
|
5.2.4.2. HTTP Service Clients(グループ単位で設定を集約して一括登録する場合)のセットアップ¶
@ImportHttpServicesアノテーションとHttpServiceGroupConfigurerを利用してグループ単位で一括で設定を行う場合は、以下の手順でセットアップを行う。
@HttpExchangeアノテーションを付与したインターフェースを作成する。@HttpExchangeアノテーションを付与したインターフェースをHTTPサービスグループに登録し、HTTPサービスグループ単位で設定を行う。Note
HTTPサービスグループとは
同じクライアント設定と
HttpServiceProxyFactoryを共有する@HttpExchangeを付与したインターフェースの集合である。 グループ定義は@ImportHttpServicesで行い、定義したグループ単位のクライアント設定はHttpServiceGroupConfigurerにて適用する。- RESTクライアントを利用するコンポーネントにて、
@HttpExchangeアノテーションを付与したインターフェースの動的プロキシをインジェクションする。
5.2.4.2.1. HTTP Service Clientsの定義(グループ単位で設定を集約して一括登録する場合)¶
@HttpExchangeアノテーションを付与したインターフェースを作成する。5.2.4.2.2. HTTP Service ClientsのBean定義(グループ単位で設定を集約して一括登録する場合)¶
HTTP Service ClientsをHTTPサービスグループに登録し、HTTPサービスグループ単位で設定を行う。import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
import org.springframework.web.service.registry.ImportHttpServices;
// (1)
@Configuration
@ImportHttpServices(group = "first", types = { UserApi.class }) // (2)
@ImportHttpServices(group = "second", basePackages = "com.example.restclient.api") // (3)
public class HttpServiceClientsConfig {
private static final Logger logger = LoggerFactory.getLogger(HttpServiceClientsConfig.class);
@Value("${api.baseUrl:http://localhost:8080}")
private String url;
@Bean
@Order(0) // (4)
RestClientHttpServiceGroupConfigurer defaults() {
// (5)
return groups -> groups.forEachGroup((group, clientBuilder, factoryBuilder) -> {
// (6)
clientBuilder.baseUrl(url)
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.defaultHeader("Default-Header", "TestHeader")
.requestInterceptor((request, body, execution) -> {
if (logger.isInfoEnabled()) {
var requestBody = new String(body, StandardCharsets.UTF_8);
logger.info("Request Header {}", request.getHeaders());
logger.info("Request Body {}", requestBody);
}
ClientHttpResponse response = execution.execute(request, body);
if (logger.isInfoEnabled()) {
logger.info("Response Header {}", response.getHeaders());
logger.info("Response Status Code {}", response.getStatusCode());
}
return response;
});
});
}
@Bean
@Order(1) // (4)
RestClientHttpServiceGroupConfigurer firstGroup() {
// (7)
return groups -> groups.filterByName("first").forEachGroup((group, clientBuilder, factoryBuilder) -> {
clientBuilder.defaultHeader("First-Header", "TestHeader");
});
}
@Bean
@Order(2) // (4)
RestClientHttpServiceGroupConfigurer secondGroup() {
// (7)
return groups -> groups.filterByName("second").forEachGroup((group, clientBuilder, factoryBuilder) -> {
clientBuilder.defaultHeader("Second-Header", "TestHeader");
});
}
}
項番 |
説明 |
|---|---|
(1)
|
HTTP Service ClientsのBean定義を行うクラスを作成する。ApplicationContextConfigに記載しても問題はないが、他の定義と混在し煩雑になりやすいため、別クラスに分けることを推奨する。ApplicationContextConfigには、@Import({ HttpServiceClientsConfig.class })を追加し、HttpServiceClientsConfigの定義を取り込む。 |
(2)
|
@ImportHttpServicesアノテーションを付与し、HTTP Service ClientsをHTTPサービスグループに登録する。クラス単位で登録する場合は、
group属性にグループ名を指定し、types属性にHTTP Service Clientsのインターフェースの型(クラス)を指定する。同一グループに複数の
HTTP Service Clientsを登録する場合は、types属性に配列で指定する。(2)、(3)は設定方法の違いを示すために両方記載しているが、どちらか一方の方法で登録すれば問題ない。
|
(3)
|
@ImportHttpServicesアノテーションを付与し、HTTP Service ClientsをHTTPサービスグループに登録する。パッケージを指定して一括で登録する場合は、
group属性にグループ名を指定し、basePackages属性に登録するパッケージ名を指定する。basePackagesを指定することにより、パッケージ配下に存在する@HttpExchangeアノテーションを付与した全てのインターフェースが登録される。(2)、(3)は設定方法の違いを示すために両方記載しているが、どちらか一方の方法で登録すれば問題ない。
また、HTTPサービスグループは、他にも手動で登録する方法などもあるので、詳しくは「HTTP Service Clients - HTTP Service groups」を参照されたい。
|
(4)
|
@Orderアノテーションを付与し、HTTPサービスグループの設定順序を指定する。上記の例では、
defaultsメソッドで全てのグループに対して共通の設定を行った後に、firstGroupメソッドとsecondGroupメソッドでそれぞれのグループに対して個別の設定を行っている。Warning Orderアノテーションの設定順序について
|
(5)
|
org.springframework.web.client.support.RestClientHttpServiceGroupConfigurerを利用して、HTTPサービスグループの設定を行う。RestClientHttpServiceGroupConfigurerは、@FunctionalInterfaceで提供されているため、関数にて設定を行う。上記の例では、グループをフィルタリングせずに
forEachGroupメソッドを使用して全てのグループに対して設定を行っている。 |
(6)
|
forEachGroupの引数としてDefaultRestClientBuilderが引き渡されるため、必要に応じて共通のカスタマイズ設定を行う。設定内容は
RestClientと同様のため、以下を参照されたい。 |
(7)
|
グループ単位で設定する場合は、
filterByNameメソッドを使用してグループをフィルタリングして設定を行う。 |
Note
HttpServiceGroupConfigurerについて
HttpServiceGroupConfigurerのサブインターフェースとして、RestClientHttpServiceGroupConfigurerとWebClientHttpServiceGroupConfigurerが定義されている。これらのインターフェースが、DefaultRestClientBuilderやDefaultWebClientBuilderを仲介し、RestClientやWebClientの設定を行っている。したがって、同期通信を行う場合はRestClientHttpServiceGroupConfigurerを、非同期通信を行う場合はWebClientHttpServiceGroupConfigurerを使用する必要がある。
5.2.4.2.3. HTTP Service Clientsの利用(グループ単位で設定を集約して一括登録する場合)¶
HTTP Service ClientsのBeanをインジェクションする。HTTP Service Clientsの利用に関しては、インターフェース単位で設定・登録する場合と同様のため「HTTP Service Clientsの利用(インターフェース単位で設定・登録する場合)」を参照されたい。5.2.4.3. データの取得(GETリクエスト送信)¶
HTTP Service Clientsを使用したデータ取得について説明を行う。5.2.4.3.1. GETメソッドを指定したリクエスト送信の実装¶
HTTP Service Clientsのインターフェースの定義である。HTTP Service Clientsの定義
// (1)
@GetExchange(value = "users/{id}") // (2)
User getUser(@PathVariable("id") String id); // (3)
項番 |
説明 |
|---|---|
(1)
|
返却値としてレスポンスボディの型を定義する。
返却値についての説明は、次項の「HTTP Service Clientsの返却値について」にて説明を行う。
|
(2)
|
HTTPのメソッドにGETを指定する場合は、
@GetExchangeアノテーションを付与する。value属性にはURLを指定する。@HttpExchangeのmethod属性にGETを指定した場合と同等である。 |
(3)
|
URIテンプレート変数の値を設定する場合は、メソッドに引数を定義し
@PathVariableアノテーションを付与する。アノテーションの引数にはURIテンプレート変数の名前を指定する。
|
5.2.4.3.2. HTTP Service Clientsの返却値について¶
HTTP Service Clientsで指定できる返却値は、他のRESTクライアントと同様でリクエストボディの型とResponseEntityが指定可能である。HTTP Service Clientsの定義
@GetExchange(value = "users/{id}")
User getUser(@PathVariable("id") String id); // (1)
@GetExchange(value = "users/{id}")
ResponseEntity<User> getUserResponseEntity(@PathVariable("id") String id); // (2)
項番 |
説明 |
|---|---|
(1)
|
返却値としてレスポンスボディの型を定義する。
レスポンスボディのみ取得したい場合に使用する。
|
(2)
|
返却値として
ResponseEntity<T>を定義する。総称型にはレスポンスボディの型を指定する。
HTTPステータスコード、レスポンスヘッダ、レスポンスボディを取得する必要がある場合に使用する。
|
5.2.4.4. データの登録(POSTリクエスト送信)¶
HTTP Service Clientsを使用したデータ登録について説明を行う。5.2.4.4.1. POSTメソッドを指定したリクエスト送信の実装¶
HTTP Service Clientsのインターフェースの定義である。HTTP Service Clientsの定義
@PostExchange(value = "users") // (1)
User createUser(@RequestBody User user); // (2)
項番 |
説明 |
|---|---|
(1)
|
HTTPのメソッドにPOSTを指定する場合は、
@PostExchangeアノテーションを付与する。value属性にはURLを指定する。 |
(2)
|
リクエストボディに値を設定する場合は、メソッドに引数を定義し
@RequestBodyアノテーションを付与する。 |
5.2.4.5. リクエストヘッダの設定¶
HTTP Service Clientsのインターフェースで定義できるのは、後者のリクエスト送信単位でヘッダを設定する方法である。HTTP Service Clientsを利用したリクエスト送信単位でヘッダを設定する実装例である。HTTP Service Clientsの定義
@GetExchange(value = "users/{id}")
User getUserSetHeader(@RequestHeader("Custom-Header") String customValue, // (1)
@PathVariable("id") String id);
項番 |
説明 |
|---|---|
(1)
|
リクエストヘッダを設定する場合は、
@RequestHeaderアノテーションを付与する。アノテーションの引数にはヘッダ名を指定する。
|
5.2.4.6. エラーハンドリング¶
HTTP Service Clientsを利用した際のエラーのハンドリングに関しては、同期通信のエラーハンドリングと同様でtry-catch文を使用して例外ハンドリングを行う。5.2.4.7. ファイルアップロード(マルチパートリクエスト)¶
HTTP Service Clientsにおけるファイルアップロードについて説明を行う。HTTP Service Clientsでファイルアップロード(マルチパートリクエスト)を行うには、@RequestPartアノテーションを使用してリクエストボディにファイルを設定する必要がある。HTTP Service Clientsの定義
@PostExchange(value = "users/upload")
void upload(@RequestPart("file") Resource resource); // (1)
項番 |
説明 |
|---|---|
(1)
|
マルチパートデータを設定する場合は、
@RequestPartアノテーションを付与する。アノテーションの引数にはパート名を指定し、ファイルを渡す際の型は
Resource型を指定する。なお、ファイルを渡す際の型は
MultipartFile型を指定することも可能である。 |
HTTP Service Clientsの呼び出し
@Override
public void upload(String path) {
var resource = new ClassPathResource(path); // (1)
userApi.upload(resource); // (2)
}
項番 |
説明 |
|---|---|
(1)
|
ファイルをResourceの実装クラスを使用して取得する。
|
(2)
|
HTTP Service Clientsのメソッドに(1)のResourceを引数として設定し、アップロードを実行する。 |
5.2.4.8. ファイルダウンロード¶
HTTP Service Clientsでは返却値としてInputStreamやResourceなどの型を指定することで、レスポンスボディをファイルとして受け取ることが出来る。HTTP Service ClientsにおいてもResourceを返却値に指定した場合はダウンロードするファイルのサイズによっては、java.lang.OutOfMemoryErrorが発生する可能性があるため、基本的にはInputStreamを返却値に指定する実装を推奨する。Resourceを返却値に指定した実装を検討するとよい。レスポンスボディをInputStream型で取得する実装例
HTTP Service Clientsを利用して、レスポンスボディをInputStream型で受け取りダウンロードする場合の実装例を示す。HTTP Service Clientsの定義
@GetExchange(value = "users/download")
InputStream download(); // (1)
項番 |
説明 |
|---|---|
(1)
|
返却値に
InputStream型を指定する。 |
HTTP Service Clientsの呼び出し
インポート宣言
import org.springframework.util.FileCopyUtils;
メソッド
@Override
public void download() {
try(InputStream in = userApi.download()) { // (1)
File rcvFile = File.createTempFile("rcvFile", "zip"); // (2)
FileCopyUtils.copy(in, new FileOutputStream(rcvFile)); // (3)
// omitted
};
}
項番 |
説明 |
|---|---|
(1)
|
HTTP Service Clientsのメソッドを呼び出し、ダウンロードを実行する。 |
(2)
|
出力するファイルを生成する。
|
(3)
|
FileCopyUtilsを使用して、(2)で生成したファイルにレスポンスボディを少しずつ書き出す。 |
レスポンスボディをResource型で取得する実装例
HTTP Service Clientsを利用して、レスポンスボディをResource型で受け取りダウンロードする場合の実装例を示す。ResourceHttpMessageConverterがレスポンスボディを一度byte配列として読み込むため、レスポンスボディ全体がメモリ上に展開される。HTTP Service Clientsの定義
@GetExchange(value = "users/download")
Resource download(); // (1)
項番 |
説明 |
|---|---|
(1)
|
返却値に
Resource型を指定する。 |
HTTP Service Clientsの呼び出し
@Override
public void download() {
Resource resource = userApi.download(); // (1)
// omitted
}
項番 |
説明 |
|---|---|
(1)
|
HTTP Service Clientsのメソッドを呼び出し、ダウンロードを実行する。ダウンロード後、必要に応じて
Resourceを使用した処理を実装する。 |
5.2.5. How to extend(HTTP Service Clients)¶
本節では、HTTP Service Clients実装の拡張方法について説明する。
5.2.5.1. HTTP Service ClientsでのURIテンプレート変数値のフォーマット変換¶
HTTP Service Clientsを利用し、URIテンプレート変数の値をフォーマット変換する場合は、ConversionServiceを利用して実装する必要がある。HTTP Service Clientsで実現する場合に、UriBuilderではなくConversionServiceで変換処理を実装する理由を説明する。HTTP Service Clientsを利用した場合もUriBuilderの適用は行われるため、カスタマイズしたUriBuilderも動作するが、HTTP Service Clientsで定義しているデータ型の変換にSpring Frameworkの型変換の仕組みであるConversionServiceが利用されており、そのConversionServiceがUriBuilderより先に動作するため、意図した変換が行われない。UriBuilderより先にConversionServiceがURIテンプレート変数を文字列型に変換するため、ConversionServiceの後に動作するUriBuilderは期待するデータ型(LocalDate等)ではなく文字列型で変換処理が呼び出され、意図したフォーマットでの変換が行われない。HTTP Service Clientsを利用する場合は、ConversionServiceを利用して変換処理を実装する必要がある。HTTP Service ClientsでURIテンプレート変数の値(LocalDate)を、特定のフォーマット文字列に変換するConversionServiceの実装例である。URIテンプレート:
http://localhost:8080/api/order/{date}URIテンプレート変数の値:
LocalDate.of(2025, 1, 1);送信時のURL:
http://localhost:8080/api/order/20250101
Bean定義ファイルの定義例(インターフェース単位で設定・登録する場合)
HttpServiceProxyFactoryを利用したインターフェース単位での設定方法は、以下の通りである。
ApplicationContextConfig.java
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
// (1)
@Bean("dateConversionService")
public ConversionService dateConversionService() {
var conversionService = new DefaultConversionService(); // (2)
conversionService.addConverter(LocalDate.class, String.class, d -> formatter.format(d)); // (3)
return conversionService;
}
@Bean("userApi")
public UserApi userApi() {
RestClientAdapter adapter = RestClientAdapter.create(restClient());
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter)
.conversionService(dateConversionService()).build(); // (4)
return factory.createClient(UserApi.class);
}
項番 |
説明 |
|---|---|
(1)
|
org.springframework.core.convert.ConversionServiceのBean定義を行う。 |
(2)
|
org.springframework.core.convert.support.DefaultConversionServiceのインスタンスを生成する。 |
(3)
|
DefaultConversionServiceのaddConverterメソッドを使用して、Converterを登録する。引数には、変換前の型、変換後の型、変換処理を設定する。
|
(4)
|
HttpServiceProxyFactory.BuilderのconversionServiceメソッドを使用して、(1)のConversionServiceを登録する。 |
Bean定義ファイルの定義例(グループ単位で設定を集約して一括登録する場合)
@ImportHttpServicesアノテーションとHttpServiceGroupConfigurerを利用してグループ単位で一括で設定を行う場合は、以下の通りである。
HttpServiceClientsConfig.java
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
// (1)
@Bean("dateConversionService")
public ConversionService dateConversionService() {
var conversionService = new DefaultConversionService();
conversionService.addConverter(LocalDate.class, String.class, d -> formatter.format(d));
return conversionService;
}
@Bean
@Order(0)
RestClientHttpServiceGroupConfigurer defaults() {
return groups -> groups.forEachGroup((group, clientBuilder, factoryBuilder) -> {
clientBuilder.baseUrl(url)
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.defaultHeader("Default-Header", "TestHeader")
.requestInterceptor((request, body, execution) -> {
if (logger.isInfoEnabled()) {
var requestBody = new String(body, StandardCharsets.UTF_8);
logger.info("Request Header {}", request.getHeaders());
logger.info("Request Body {}", requestBody);
}
ClientHttpResponse response = execution.execute(request, body);
if (logger.isInfoEnabled()) {
logger.info("Response Header {}", response.getHeaders());
logger.info("Response Status Code {}", response.getStatusCode());
}
return response;
});
factoryBuilder.conversionService(dateConversionService()); // (2)
});
}
項番 |
説明 |
|---|---|
(1)
|
org.springframework.core.convert.ConversionServiceのBean定義を行う。実装に関しては、インターフェース単位で設定・登録する場合と同様である。
|
(2)
|
引数で渡される
HttpServiceProxyFactory.BuilderのconversionServiceメソッドを使用して、(1)のConversionServiceを登録する。 |
5.2.5.2. HTTP Service Clientsの共通処理¶
HTTP Service ClientsではexchangeAdapterDecoratorを利用することで、HTTP Service Clientsでの共通処理を実装することができる。RestClientやRestTemplateで利用するClientHttpRequestInterceptorと異なり、リクエストの送信前後ではなくHTTP Service Clientsのアダプタの実行前後に処理が実行される。HTTP Service Clientsの例外処理や、返却値の加工などの共通処理を実装することができる。nullで返却する機能を提供している。exchangeAdapterDecoratorの実装について説明を行う。NotFoundRestClientAdapterDecoratorを利用した例となり、404エラー(NOT_FOUND)の場合にもエラーとせずレスポンスボディをnullで返却する設定である。Bean定義ファイルの定義例(インターフェース単位で設定・登録する場合)
HttpServiceProxyFactoryを利用したインターフェース単位での設定方法は、以下の通りである。
ApplicationContextConfig.java
@Bean("userApi")
public UserApi userApi() {
RestClientAdapter adapter = RestClientAdapter.create(restClient());
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter)
.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new).build(); // (1)
return factory.createClient(UserApi.class);
}
項番 |
説明 |
|---|---|
(1)
|
HttpServiceProxyFactory.BuilderのexchangeAdapterDecoratorメソッドを使用して、org.springframework.web.client.support.NotFoundRestClientAdapterDecoratorのインスタンスを登録する。Note 独自のexchangeAdapterDecoratorの定義 Spring Frameworkで提供している |
Bean定義ファイルの定義例(グループ単位で設定を集約して一括登録する場合)
@ImportHttpServicesアノテーションとHttpServiceGroupConfigurerを利用してグループ単位で一括で設定を行う場合は、以下の通りである。
HttpServiceClientsConfig.java
@Bean
@Order(0)
RestClientHttpServiceGroupConfigurer defaults() {
return groups -> groups.forEachGroup((group, clientBuilder, factoryBuilder) -> {
clientBuilder.baseUrl(url)
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.defaultHeader("Default-Header", "TestHeader")
.requestInterceptor((request, body, execution) -> {
if (logger.isInfoEnabled()) {
var requestBody = new String(body, StandardCharsets.UTF_8);
logger.info("Request Header {}", request.getHeaders());
logger.info("Request Body {}", requestBody);
}
ClientHttpResponse response = execution.execute(request, body);
if (logger.isInfoEnabled()) {
logger.info("Response Header {}", response.getHeaders());
logger.info("Response Status Code {}", response.getStatusCode());
}
return response;
});
factoryBuilder.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new); // (1)
});
}
項番 |
説明 |
|---|---|
(1)
|
引数で渡される
HttpServiceProxyFactory.BuilderのexchangeAdapterDecoratorメソッドを使用して、org.springframework.web.client.support.NotFoundRestClientAdapterDecoratorのインスタンスを登録する。Note 独自のexchangeAdapterDecoratorの定義 Spring Frameworkで提供している |
5.2.6. Appendix¶
5.2.6.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.6.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.6.2.1. 非同期通信のセットアップ¶
項番 |
手順 |
|---|---|
(1)
|
pom.xmlにSpring WebFluxの依存関係を追加する。 |
(2)
|
WebClientのBean定義を行い、DIコンテナに登録する。 |
(3)
|
WebClientを利用するコンポーネントにて、(2)のBeanをインジェクションする。 |
5.2.6.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.6.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.6.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.6.2.3. エラーハンドリング¶
5.2.6.2.3.1. サーバとの通信結果をハンドリングする方法¶
WebClientのStatusHandlerは、HTTPステータスコードをもとにエラーの判定を行うためのインタフェースである。RestClientと同様で、アプリケーション全体でエラーハンドリングを行う場合と、リクエスト毎でエラーハンドリングを行う場合の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.6.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.6.2.4. リクエスト送信前の共通処理の適用¶
org.springframework.web.reactive.function.client.ExchangeFilterFunctionを使用した共通処理の実装方法について説明を行う。
5.2.6.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を登録したい場合は、
リストに登録された順番に処理されるようになる。 |