SpringBootでのRestTemplateのタイムアウト設定
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
だとここで見たコードも結構変わってたりてて面白かった。
nullが嫌い
消したつももりだった下書きが残ってたっぽいので公開しちゃう。なお2015-05-10に書いたもののようです。当時のブログを見ると、Optionalでなんか盛り上がった頃みたい。
ということで以下は編集せずそのまま投稿ー。
前置き
nullを安易に使わない 派生のポエムです。
null
が嫌いってのは上記に限らずよく書いてる気がするけど、嫌いって感情だけでポエムったらこんなになってました。
なお、Javaの話です。
nullが嫌い
null
が嫌い。気軽に使われるnull
が嫌い。思考停止で使われるnull
が大嫌い。一時変数を宣言して初期化とか言って使われるnull
が嫌い。まともに扱いきれないのに簡単に使えてしまうnull
が大嫌い。
NullPointerException
が発生した時に「開発者の注意不足で発生した。しっかりチェックして再発防止に努める。」などと言わされたり(やりもしないのに)、責める要因に使われるのを見ると虫酸が走る。「null
の可能性に注意を払えなかったこと」を責めたててどうするのだろう。問題はそこじゃないよね。そりゃ気をつけてて、偶然気づければ防げるかもしれない。でも人間は注意し続けることはできない。チェックリストを作っても、チェックは漏れる。チェックすれば救われる教徒のチェックリストはおもしろい長さになるし、運用もザルになるものだ。「チェックOKが記入されているチェックリストをコピーして一部修正」なんてのも、よくある話。チェックリストが長すぎるのが悪い。チェック漏れが起こったらチェックリストのチェックリストを作りはじめる。冗談のように聞こえるけれど、稀によく見る。「気をつけます」なんてのは、「気をつけ損なったら仕方ない」の宣言に他ならない。なので、漏れる。気をつけたのに漏れた場合は、気をつけてなかった以外に理由は出てこない。気をつけ漏れないように気をつける、無限ループ。
確かに、注意でしか防げないことはある。あるだろうよ。そういうものはチェックを二重化、三重化して漏らす可能性を可能な限り減らす。それでも絶対はない。多重化されたチェック機構はコストに対する効果がどんどん薄れていくものである。そして「null
チェック」にそこまでのコストを割くことはできない。超軽く扱われてる。だったら漏らしていいってことだよね。ダメ?ダメだったら「注意」以外のアプローチが必要だろう。とは言え、今まで「注意する」でやってきたものがいきなり他のうまくいく方法を考え出せるでもないし、完全にうまくいく方法なんて存在するわけでもない。よく言われるように、銀の弾丸なんて無い。他の方法を考えた時に完全性とか言い始めると何もできない。シンプルにメリデメの天秤にかけるしかない。
……と言う話をしても「丁寧にnull
チェックすれば良い」と主張されたりする。そうか、ならばNullPointerException
が起こりうるすべての場所でチェックする?できる?無理だよね。
null
に注意を払わなきゃいけない状況は、いままで安易にnull
を使ってきたツケなんだ。安易にnull
を返すメソッドが悪だし、安易にnull
に逃げる文化が悪い。その負債の一部を払うのがnull
チェック。一部しか払えず、null
チェックを行えば行うほどコードの量が増える。つまり負債が増える。負債を他所で借りてきたもので払うって自転車操業、全体でみると負債は増えてるんじゃないかな。
NullPointerException
に遭遇した際、最初にやることはおそらく「null
チェックを追加する」ではない。きっとnull
を無視して動くようにすることでもない。これらは問題を先送りしているだけ。そこで例外が起こるということは、おそらく例外的事象なんだ。null
になるべきでない状況でnull
になる現実、その根本を断ちに行かなくちゃ。でないと、いつまでたってもnull
と戯れ続けることになる。そんなに暇じゃないはず。
勿論、null
チェックが必要な場面もある。一切合切のチェックを排除できるなんて言ってないし、Javaからnull
を根絶できるとも言ってない。ここまで書いて、「私が嫌いなのはnull
そのものじゃないんだなー」と気づいた。
ArrayListじゃなくListを使うという話
具象型ではなく抽象型で扱え、インタフェースを使え、みたいなお話に対して。
前置き
Javaの話。他の言語だと話は変わります。
「こうするのが絶対的に正解」と言うものではありません。私の現在の選択の説明です。明日になったら違うこと言ってるかも。
主な登場人物は掲題の java.util.ArrayList
および java.util.List
、そして java.util.Collection
と java.lang.Iterable
です。
こんな世界観。他のインタフェースやクラスもたくさんありますが、この話の本筋では無いので触れません。
前提として以下を置いています。
- フレームワークやライブラリではなく、一つの業務アプリケーションに閉じた話です。ゆえに不特定多数から使われる型ではなく、影響を与えるコードは全て目が届く範囲にあるものとします。
- 計算量は別の話です。扱うドメインにもよりますが、
ArrayList
の計算量が問題になるのはそもそも稀ですし、今となっては設計の問題です。Stream#parallel
とかに話吹っ飛ばすと帰ってこれなくなるし……。検討の優先順位としてはかなり下位になります。 - ジェネリクスは触れません。コレクションの型の話には強い影響を与えるジェネリクスさんだけど、今回は影響ないので。
var
はスコープ外。別で扱います。そのうち。- 先頭にある通り「具象型ではなく抽象型で扱え、インタフェースを使え、みたいなお話」が主題です。今回の登場人物になっているのは、この話に対して抽象度の高低両方を話せるから。
String
とCharSequence
でもよかったんだけど、一方向だけになるし、CharSequence
はそんな馴染みがないだろうから回避です。
前提が変わったら結論も変わるかもしれませんし、結論が変わらなくても説明は変わると思います。
Short Answer
List
を使っておけばいいと思います。
特に「御託はいいからどうしたらいいの?」と言うのであれば、慣習も含めて無難な選択です。 面倒なことに煩わされずに他の重要な(と思っている)ことに注力するためのとりあえずの選択としては、悪く無いはず。
と言うことで、以下は長々とした私の選択理由の説明。
使っているところを見てみる
この話題の対象はローカル変数、フィールド、メソッドの引数と戻り値が挙げられます。
結論は List
に収束するのですが、理由が違うので分けて説明します。
自分で生成するローカル変数
List<MyEntry> list = new ArrayList<>();
ここの選択にはそれほど強い理由はないです。
メソッド等で使用するローカル変数の変数型は影響も少ないので ArrayList
としても問題はありません。
もちろん前提として、メソッドが十分に小さいこと。1000行とかなるならもうちょっと考えるかもしれない。
けどローカル変数の型をどうするかの前に考えることあるでしょ?
ローカル変数型は他のメソッドの引数にしたり、自身の戻り値として使います。
ここで ArrayList
としていると、IDEでメソッド生成したりフィールド生成したりするときに ArrayList
がデフォルトで選択されることになります。
メソッドシグネチャやフィールドでは後述の理由で List
を使いたいので、ローカル変数で List
を使う理由の一つに、手間の軽減が挙げられます。
これだけであれば手間のタイミングが前後するだけで、外に出ないインスタンスであれば ArrayList
にしておくのが良さげにも見えますが、そこに意図を込めて「ArrayList
にしていると言うことはこのスコープに閉じたインスタンスなんだな!」を読み取って貰うのは酷というか。
自分で生成しないローカル変数
メソッドの戻り値とか、わざわざ型を変えたりしません。だって面倒だし、大抵 List
になってるのでここで何か主張する必要もないわけで。
フィールド
class MyClass {
List<MyEntry> list;
}
外から直接フィールドを参照したりしなければ、自由度が高く意図を込めやすいところです。そう言う意味で一時期は Collection
や Iterable
にしていました。でもやめて List
に落ち着いています。
ここで ArrayList
にすると Collections#emptyList
や Arrays#asList
、 Collectors#toList
が使えなくなります。
そしてフィールドはメソッドで操作するものなので Collection
や Iterable
にすると足りないこともしばしばあります。
ArrayList
じゃなかったら add
メソッドとかがオプションだから危険!うっかり Collections#emptyList
とか使うと実行時エラーになる!!とかもあります。そんな時は ArrayList
にすることもあります。これはオプションのメソッドを持っていると言う List
と言うか Collection
の負債をどこで処理するかと言うことも絡みますので、そこそこ慎重な判断が必要だったり。
ちょっと脱線になりますが、クラスが Serializable
を実装している場合はフィールドも Serializable
にしないとなので、そのような場合は ArrayList
を使ったりします。
とはいえ Serializable
に関しては別の話があって、重視すべきじゃないときはそんな気にしなかったりも。まあ Serializable
要求されたからって半端に使いすぎじゃ?ってコードはよく見ます。
だって ArrayList
も ArrayList<E extends Serializable>
とかじゃないわけで……まあそんなことしちゃったら過去のコード軒並みコンパイルできなくなるからやらなかったんだろうけれど……
メソッドの引数
void method(List<MyEntity> list);
引数型は、そのメソッドが処理するために必要な制約を十分につけられる最も抽象的な型で扱うのが良いはずです。その方がいろんなところから使えるから。
なので List
ではなく Collection
か Iterable
辺りで扱うのが適切なはずなのですが、ここも List
に落ち着きます。
なぜならここまでの選択でローカル変数やフィールドがList
となっているので、引数も必然 List
でよくなるのです。
フレームワークやライブラリのように不特定多数に使用されるインタフェースならともかく、そうでないのならば不要な抽象化かと。
メソッドの戻り値
List<MyEntity> method();
戻り値型を具象型にせず List
にするのは、戻り値に対して余計な制約をつけないためと言うのもありますが、メソッド自身の戻り値はローカル変数やフィールドを返すことが多く(防御的コピーは基本的にノイズなのでしないです。取得したコレクションをいじるような行儀の悪いことしないので。)、戻り値型を ArrayList
などにするためにはキャストや新しい ArrayList
インスタンスの生成が必要になり、手間です。
Iterable
などにしない理由は、そうすると呼び出し側も Iterable
で受けざるをえなくなり、使い勝手が悪くなることがしばしばあるからです。
コレクション同士の操作、たとえば addAll
などもできないですし、contains
などが欲しくなった時に型を変える必要がでてきます。
Collection
にすればこれは解決するので Collection
にしていたこともありますが、一つ目の理由もあってわざわざ Collection
にはしていません。
ということで
全部 List
になっています。
Listにする「べき」だと言うお話とお返し
List
にするべきだ、と大上段にお話ししてくれる方もいらっしゃいます。なんでも「List
で扱うことで実装から切り離すことができ、ArrayList
や LinkedList
、その他の List
実装を選択できる。実装に依存してはいけない。」とかなんとか。他の説明をしていただけるかもしれません。なるほどなるほど。
ところで List
はJavadocに書かれているように「順序付けられたコレクション」です。つまり、 順序が不要であれば本来は List
を使うべきではありません。
順序が必要な場合も多くありますが、List
を使う際に 常に 順序が必要かと言うと、そんなことはないはずです。
コレクションが欲しいのであれば List
でなく Collection
が適切であり、全件ループできればいいだけであれば Iterable
が適切です。
と言うことで、なんか格好つけた理由で List
にするべきだとか言うのは片手落ちなのです。 なんかドヤられてイラっとしたらこの返しでだいたい大丈夫。
あと、こと業務アプリケーション開発に限定すればList
の実装型を変更するような場面は稀です。変更したことも実際ありますが、 List
にしてなくても別に問題なく変更できたと思います。
とか言ってListを使う理由
List
って、一番文字数少ないんですよ(今回の登場人物の中では。)。文字数が影響するのは書く時ではなく読む時です。書く時はIDEがなんとかするので関係ありません。文字数少ないほうが目に優しい。
繰り返しになるけれど var
は別の話ね。
で、それより重要なのがノイズの少なさです。
これは List
がよく使われるがゆえですが、余計な情報が少ないはずの Collection
や Iterable
のほうが「わざわざそれを使っている」と言うメッセージが入ってしまいます。
例えば「ん、 Collection
と言うことは Set
だったりするのかな?」とよぎってしまう。「 List
じゃなく Iterable
にしてるのは何故なんだろう」とか。
不思議なことに考えることが逆に増えてしまうわけです。これは慣習的にList
がよく使われることに起因するものかなと思っています。
先に書いた「一時期 Collection
や Iterable
を使ってみたけれどやめた」の筆頭の理由がこのノイズです。
もう一つ、型が伝播しちゃうと言うのも挙げられます。一箇所 Iterable
にしたら芋づる式に Iterable
になっていきますし、それで不足して List
に変えたらまた芋づる式に変えていく必要がでてくる。これは設計にも依存するので、ファーストクラスコレクションを基本に据えてからは芋づる式の変更はほぼ不要になりましたが。。
本当に欲しいのは、順序づけられたコレクションではなく、ノイズが少なく取り回しのよいコレクションです。
最も使い勝手の良かった ArrayList
、そのインタフェースでありほぼ全てのメソッドを備えている List
がその座を占拠してしまっています。
その結果、多くのライブラリのメソッド引数は List
を要求しているため Collection
の使い勝手は正直悪いです。
Collection
を使う方がメリットが多いはずなんですが、現実としてはデメリットがうわまってしまい、わざわざ Collection
を使う理由がないんですよね……。
弊害として、 List
を使っているのに順序が必要になったときに「こいつ順序の意識もって使われてるんだっけ?」と疑うハメになってる。順序を持つかすらオプションとか、酷い話だ。
実際やってみて欲しい
「一時期 Collection
や Iterable
を使ってみた」と書きましたが、これで「へーダメなんだ」とか「そりゃそうでしょ」で終わって欲しくない思いもあります。私が苦労したことを他の人にもやって欲しいとかいうわけではなく、実際やることを通しての気づきというか、理解の深さが変わるので。実際挑んだら説明できるようにはなる、少なくともその材料は揃う。説明も結論も私と違って全然いいのです。
この手の「実際やってみた方がいい」と言う話は、実践から得たものを形式知にしきれていないと言うことなんだけど、まあそもそも共同化できてないから表出化も難しいのだよとか自己擁護しておく。
結論
コレクションとしてこだわりがない場合は List
で扱っています。
何かの原則とかに沿ったキレイなものではなく、単に現実的な使い勝手による選択です。
冒頭で触れた String
と CharSequence
だと String
を選ぶわけですが、その説明もだいたいそんな感じです。
Java言語としては適切な型があって欲しいとは思うのですが、サードパーティのコレクションライブラリを使ってみて感じるのは、置き換える労力をかけるところでもないと言うことです。コレクションはコレクションだけで完結せず、他のライブラリやフレームワークに引き渡すインタフェース(文字通りの)となっているため、サードパーティのコレクションライブラリをうまく使うのは難しく、労力に見合わないこともしばしばです。
そう言う意味でCollectionsFrameworkは返済できない負債となってしまっているのですが、List
を使っていて不利益を被ることはほとんどないのが実状。
学び方によっては苦労しちゃうんだろうなぁ、それは残念なところかなぁ、とは思います。
まあCollectionsFrameworkなんてJava1.2時代のもの、ツッコミどころなんてない方がおかしい。にも関わらずいまだに使えてるのって凄いなぁ(ただの感想
蛇足
ところで9年前に
「List list = new ArrayList();」を定型句として扱うなんて、もってのほかです。
とか言ってた。なんか機嫌悪かったんですかね。