spring-boot:2.1.7.RELEASE
spring-web:5.1.9.RELEASE
Short Answer
RestTemplateBuilder restTemplateBuilder = ... RestTemplate restTemplate = restTemplateBuilder .setConnectTimeout(Duration.ofSeconds(3)) .setReadTimeout(Duration.ofSeconds(3)) .build();
タイムアウトが設定できない時は実行時例外が出ます。
以下は中身に興味のある人向け。
登場人物
RestTemplate
とかRestTemplateBuilder
とか
RestTemplate
は実際の通信をClientHttpRequestFactory
に任せます。
この子はインタフェースなので実際の通信は実装クラスがすることになります。
そのため通信に関わることは ClientRequestFactory
の実装が使用するHTTPクライアントに依存することになります。
今回のタイムアウトなどはこのHTTPクライアントが担うものになります。
RestTemplateBuilder
はSpringBoot1.4で導入されたRestTemplate
設定用のクラスです。
RestTemplateBuilder
があるのならRestTemplate
への設定は全部任せたいところですが、タイムアウトは前述のようにRestTemplate
に対する設定ではないのです。
これどうやってんだっけ、ってのがこれ書いた動機。
タイムアウトを実際に設定するところ
タイムアウトはHTTP通信に依存したものという判断か、RestTemplate
にはタイムアウトを設定するようなメソッドはありません。
そのためClientRequestFactory
の実装か、使用されるHTTPクライアントに設定することになります。
デフォルトで使用されるSimpleClientHttpRequestFactory
はsetConnectTimeout
/setReadTimeout
を持っており、これらの値がHttpURLConnection
に渡されます。他のクライアントでも同様だと思います。
という事で、タイムアウトを設定したClientRequestFactory
を使用するようにRestTemplate
に設定します。これにはRestTemplate
のコンストラクタかsetRequestFactory
メソッドを使用します。
SpringBootを使用しない場合はこんな感じ。
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setConnectTimeout(3_000); requestFactory.setReadTimeout(3_000); RestTemplate restTemplate = new RestTemplate(requestFactory);
SpringBootを使用する場合も最終的に前述の設定をする事には変わりありません。なのでRestTemplateBuilder
がよくわからなくても次のような方法でいいといえばいいわけです。
- いままでどおりやる。
- buildした
RestTemplate
に自分で生成したClientHttpRequestFactory
をsetRequestFactory
する。
でもタイムアウトを設定したいのであってSimpleClientHttpRequestFactory
を使いたいわけではないわけで。
SpringBootでのClientHttpRequestFactory
の決まり方
という事でClientRequestFactory
の実装にタイムアウトを設定したいのですが、まずはClientHttpRequestFactory
がどう決まるかを見ていきましょう。
RestTemplateBuilder
はRestTemplate
に対して次のようにClientHttpRequestFactory
を設定しています。(ソース)
Supplier<ClientHttpRequestFactory>
が指定されていればそれを使用してClientHttpRequestFactory
を生成する。detectRequestFactory
が有効ならClientHttpRequestFactorySupplier
を使用してClientHttpRequestFactory
を生成する。- どちらも無効なら何もしない。(
RestTemplate
のデフォルトになる。)
RestTemplateBuilder
はクラスパスにあるHTTPクライアントを自動で使用するようになっており、SpringBoot2.1.7では ApacheHttpClient -> OkHttp3 -> JDK
となっているようです。これはClientHttpRequestFactorySupplier
で実現されます。(ソース)
先に「SimpleClientHttpRequestFactory
がClientHttpRequestFactory
のデフォルトになる」というような事を書きましたが、SpringBootを使わない場合はRestTemplate
(というか基底のHttpAccessor
)のデフォルトであり、SpringBootを使う場合はRestTemplateBuilder
のClientHttpRequestFactorySupplier
のデフォルトであります。場合によってはSpringBootを使う場合と使わない場合でClientHttpRequestFactory
のデフォルトが違うなんてことも起こらないとは言わない。
タイムアウトを設定したClientHttpRequestFactory
のインスタンスを返すSupplier<ClientHttpRequestFactory>
を使うようにすればRestTemplate
のsetRequestFactory
をしなくてもよくなります。これでもいいですね。
RestTemplateBuilder
でのタイムアウト設定
RestTemplateBuilder
を使用するならClientRequestFactory
の実装を気にせずタイムアウトを設定したいものです。
具体的にはSimpleClientHttpRequestFactory
のような実装クラスを目にしたくない。いや別に恨みがあるとかではないですが。
ここで冒頭の設定方法にようやく辿りつきます。長かった。
RestTemplateBuilder
のsetConnectTimeout
/setReadTimeout
はClientRequestFactory
のインスタンスに対するリフレクションで実現されています。(ソース)
該当するメソッドがない場合は実行時に例外が出るので安心ですね。 安心じゃねーよ。とはいえ無視されてタイムアウト効かずに動くよりはマシか……
しかしこれなんでClientRequestFactory
にメソッド追加……は他の実装に配慮した(default
メソッドはJava7以前の互換で使えないし)として、新しいインタフェース作ったりしなかったんだろう。まあインタフェースにしても実行時例外には変わらないけど。
寄り道: MockRestServiceServerの話
spring-test
のMockRestServiceServer
はRestTemplate
のClientHttpRequestFactory
を差し替えることで実現されています。
つまり、ClientHttpRequestFactory
の実装に依存するテストは行えません。
こちらを動作させたい場合はMockServerなどのテストダブルをHTTPの先に置くことになります。
spring-boot-test
の@AutoConfigureMockRestServiceServer
を使うと RestTemplateBuilder
で生成された RestTemplate
に自動でバインドされます。これは RestTemplateCustomizer
の実装であるMockServerRestTemplateCustomizer
がRestTemplateBuilder
に設定されるから。便利なんですが、RestTemplateBuilder
で複数のRestTemplate
を作ってるとちょっと使いづらくなるのが残念なところ。
長々書いたけど
実際のとこRestTemplateBuilder
に対してIDE経由でお伺いをたてたら一瞬で見つかります。
あと、最新の master
だとここで見たコードも結構変わってたりてて面白かった。