日々常々

ふつうのプログラマがあたりまえにしたいこと。

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();

タイムアウトが設定できない時は実行時例外が出ます。

以下は中身に興味のある人向け。

登場人物

f:id:irof:20190817195852p:plain

RestTemplateとかRestTemplateBuilderとか

RestTemplateは実際の通信をClientHttpRequestFactoryに任せます。 この子はインタフェースなので実際の通信は実装クラスがすることになります。 そのため通信に関わることは ClientRequestFactoryの実装が使用するHTTPクライアントに依存することになります。 今回のタイムアウトなどはこのHTTPクライアントが担うものになります。

RestTemplateBuilderはSpringBoot1.4で導入されたRestTemplate設定用のクラスです。 RestTemplateBuilderがあるのならRestTemplateへの設定は全部任せたいところですが、タイムアウトは前述のようにRestTemplateに対する設定ではないのです。 これどうやってんだっけ、ってのがこれ書いた動機。

タイムアウトを実際に設定するところ

タイムアウトはHTTP通信に依存したものという判断か、RestTemplateにはタイムアウトを設定するようなメソッドはありません。 そのためClientRequestFactoryの実装か、使用されるHTTPクライアントに設定することになります。

デフォルトで使用されるSimpleClientHttpRequestFactorysetConnectTimeout/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に自分で生成したClientHttpRequestFactorysetRequestFactoryする。

でもタイムアウトを設定したいのであってSimpleClientHttpRequestFactoryを使いたいわけではないわけで。

SpringBootでのClientHttpRequestFactoryの決まり方

という事でClientRequestFactoryの実装にタイムアウトを設定したいのですが、まずはClientHttpRequestFactoryがどう決まるかを見ていきましょう。

RestTemplateBuilderRestTemplateに対して次のようにClientHttpRequestFactoryを設定しています。(ソース

  • Supplier<ClientHttpRequestFactory>が指定されていればそれを使用してClientHttpRequestFactoryを生成する。
  • detectRequestFactoryが有効ならClientHttpRequestFactorySupplierを使用してClientHttpRequestFactoryを生成する。
  • どちらも無効なら何もしない。(RestTemplateのデフォルトになる。)

RestTemplateBuilderはクラスパスにあるHTTPクライアントを自動で使用するようになっており、SpringBoot2.1.7では ApacheHttpClient -> OkHttp3 -> JDK となっているようです。これはClientHttpRequestFactorySupplierで実現されます。(ソース

先に「SimpleClientHttpRequestFactoryClientHttpRequestFactoryのデフォルトになる」というような事を書きましたが、SpringBootを使わない場合はRestTemplate(というか基底のHttpAccessor)のデフォルトであり、SpringBootを使う場合はRestTemplateBuilderClientHttpRequestFactorySupplierのデフォルトであります。場合によってはSpringBootを使う場合と使わない場合でClientHttpRequestFactoryのデフォルトが違うなんてことも起こらないとは言わない。

タイムアウトを設定したClientHttpRequestFactoryインスタンスを返すSupplier<ClientHttpRequestFactory>を使うようにすればRestTemplatesetRequestFactoryをしなくてもよくなります。これでもいいですね。

RestTemplateBuilderでのタイムアウト設定

RestTemplateBuilderを使用するならClientRequestFactoryの実装を気にせずタイムアウトを設定したいものです。 具体的にはSimpleClientHttpRequestFactoryのような実装クラスを目にしたくない。いや別に恨みがあるとかではないですが。

ここで冒頭の設定方法にようやく辿りつきます。長かった。

RestTemplateBuildersetConnectTimeout/setReadTimeoutClientRequestFactoryインスタンスに対するリフレクションで実現されています。(ソース) 該当するメソッドがない場合は実行時に例外が出るので安心ですね。 安心じゃねーよ。とはいえ無視されてタイムアウト効かずに動くよりはマシか……

しかしこれなんでClientRequestFactoryにメソッド追加……は他の実装に配慮した(defaultメソッドはJava7以前の互換で使えないし)として、新しいインタフェース作ったりしなかったんだろう。まあインタフェースにしても実行時例外には変わらないけど。

寄り道: MockRestServiceServerの話

spring-testMockRestServiceServerRestTemplateClientHttpRequestFactoryを差し替えることで実現されています。 つまり、ClientHttpRequestFactoryの実装に依存するテストは行えません。 こちらを動作させたい場合はMockServerなどのテストダブルをHTTPの先に置くことになります。

spring-boot-test@AutoConfigureMockRestServiceServer を使うと RestTemplateBuilder で生成された RestTemplate に自動でバインドされます。これは RestTemplateCustomizerの実装であるMockServerRestTemplateCustomizerRestTemplateBuilderに設定されるから。便利なんですが、RestTemplateBuilderで複数のRestTemplateを作ってるとちょっと使いづらくなるのが残念なところ。

長々書いたけど

実際のとこRestTemplateBuilderに対してIDE経由でお伺いをたてたら一瞬で見つかります。

f:id:irof:20190817193315p:plain

あと、最新の 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.Collectionjava.lang.Iterable です。

f:id:irof:20190809210315p:plain

こんな世界観。他のインタフェースやクラスもたくさんありますが、この話の本筋では無いので触れません。

前提として以下を置いています。

  • フレームワークやライブラリではなく、一つの業務アプリケーションに閉じた話です。ゆえに不特定多数から使われる型ではなく、影響を与えるコードは全て目が届く範囲にあるものとします。
  • 計算量は別の話です。扱うドメインにもよりますが、 ArrayList の計算量が問題になるのはそもそも稀ですし、今となっては設計の問題です。 Stream#parallel とかに話吹っ飛ばすと帰ってこれなくなるし……。検討の優先順位としてはかなり下位になります。
  • ジェネリクスは触れません。コレクションの型の話には強い影響を与えるジェネリクスさんだけど、今回は影響ないので。
  • varはスコープ外。別で扱います。そのうち。
  • 先頭にある通り「具象型ではなく抽象型で扱え、インタフェースを使え、みたいなお話」が主題です。今回の登場人物になっているのは、この話に対して抽象度の高低両方を話せるから。StringCharSequence でもよかったんだけど、一方向だけになるし、 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;
}

外から直接フィールドを参照したりしなければ、自由度が高く意図を込めやすいところです。そう言う意味で一時期は CollectionIterable にしていました。でもやめて List に落ち着いています。

ここで ArrayList にすると Collections#emptyListArrays#asListCollectors#toList が使えなくなります。 そしてフィールドはメソッドで操作するものなので CollectionIterable にすると足りないこともしばしばあります。

ArrayList じゃなかったら add メソッドとかがオプションだから危険!うっかり Collections#emptyList とか使うと実行時エラーになる!!とかもあります。そんな時は ArrayList にすることもあります。これはオプションのメソッドを持っていると言う List と言うか Collection の負債をどこで処理するかと言うことも絡みますので、そこそこ慎重な判断が必要だったり。

ちょっと脱線になりますが、クラスが Serializable を実装している場合はフィールドも Serializable にしないとなので、そのような場合は ArrayList を使ったりします。 とはいえ Serializable に関しては別の話があって、重視すべきじゃないときはそんな気にしなかったりも。まあ Serializable 要求されたからって半端に使いすぎじゃ?ってコードはよく見ます。 だって ArrayListArrayList<E extends Serializable> とかじゃないわけで……まあそんなことしちゃったら過去のコード軒並みコンパイルできなくなるからやらなかったんだろうけれど……

メソッドの引数

void method(List<MyEntity> list);

引数型は、そのメソッドが処理するために必要な制約を十分につけられる最も抽象的な型で扱うのが良いはずです。その方がいろんなところから使えるから。 なので List ではなく CollectionIterable 辺りで扱うのが適切なはずなのですが、ここも List に落ち着きます。

なぜならここまでの選択でローカル変数やフィールドがList となっているので、引数も必然 List でよくなるのです。 フレームワークやライブラリのように不特定多数に使用されるインタフェースならともかく、そうでないのならば不要な抽象化かと。

メソッドの戻り値

List<MyEntity> method();

戻り値型を具象型にせず List にするのは、戻り値に対して余計な制約をつけないためと言うのもありますが、メソッド自身の戻り値はローカル変数やフィールドを返すことが多く(防御的コピーは基本的にノイズなのでしないです。取得したコレクションをいじるような行儀の悪いことしないので。)、戻り値型を ArrayList などにするためにはキャストや新しい ArrayList インスタンスの生成が必要になり、手間です。

Iterable などにしない理由は、そうすると呼び出し側も Iterable で受けざるをえなくなり、使い勝手が悪くなることがしばしばあるからです。 コレクション同士の操作、たとえば addAll などもできないですし、contains などが欲しくなった時に型を変える必要がでてきます。 Collection にすればこれは解決するので Collection にしていたこともありますが、一つ目の理由もあってわざわざ Collection にはしていません。

ということで

全部 List になっています。

Listにする「べき」だと言うお話とお返し

List にするべきだ、と大上段にお話ししてくれる方もいらっしゃいます。なんでも「List で扱うことで実装から切り離すことができ、ArrayListLinkedList 、その他の List 実装を選択できる。実装に依存してはいけない。」とかなんとか。他の説明をしていただけるかもしれません。なるほどなるほど。

ところで ListJavadocに書かれているように「順序付けられたコレクション」です。つまり、 順序が不要であれば本来は List を使うべきではありません。 順序が必要な場合も多くありますが、Listを使う際に 常に 順序が必要かと言うと、そんなことはないはずです。 コレクションが欲しいのであれば List でなく Collection が適切であり、全件ループできればいいだけであれば Iterable が適切です。 と言うことで、なんか格好つけた理由で List にするべきだとか言うのは片手落ちなのです。 なんかドヤられてイラっとしたらこの返しでだいたい大丈夫。

あと、こと業務アプリケーション開発に限定すればListの実装型を変更するような場面は稀です。変更したことも実際ありますが、 List にしてなくても別に問題なく変更できたと思います。

とか言ってListを使う理由

List って、一番文字数少ないんですよ(今回の登場人物の中では。)。文字数が影響するのは書く時ではなく読む時です。書く時はIDEがなんとかするので関係ありません。文字数少ないほうが目に優しい。 繰り返しになるけれど var は別の話ね。

で、それより重要なのがノイズの少なさです。 これは List がよく使われるがゆえですが、余計な情報が少ないはずの CollectionIterable のほうが「わざわざそれを使っている」と言うメッセージが入ってしまいます。 例えば「ん、 Collection と言うことは Set だったりするのかな?」とよぎってしまう。「 List じゃなく Iterable にしてるのは何故なんだろう」とか。 不思議なことに考えることが逆に増えてしまうわけです。これは慣習的にListがよく使われることに起因するものかなと思っています。 先に書いた「一時期 CollectionIterable を使ってみたけれどやめた」の筆頭の理由がこのノイズです。

もう一つ、型が伝播しちゃうと言うのも挙げられます。一箇所 Iterable にしたら芋づる式に Iterable になっていきますし、それで不足して List に変えたらまた芋づる式に変えていく必要がでてくる。これは設計にも依存するので、ファーストクラスコレクションを基本に据えてからは芋づる式の変更はほぼ不要になりましたが。。

本当に欲しいのは、順序づけられたコレクションではなく、ノイズが少なく取り回しのよいコレクションです。 最も使い勝手の良かった ArrayList 、そのインタフェースでありほぼ全てのメソッドを備えている List がその座を占拠してしまっています。 その結果、多くのライブラリのメソッド引数は List を要求しているため Collection の使い勝手は正直悪いです。 Collection を使う方がメリットが多いはずなんですが、現実としてはデメリットがうわまってしまい、わざわざ Collection を使う理由がないんですよね……。

弊害として、 List を使っているのに順序が必要になったときに「こいつ順序の意識もって使われてるんだっけ?」と疑うハメになってる。順序を持つかすらオプションとか、酷い話だ。

実際やってみて欲しい

「一時期 CollectionIterable を使ってみた」と書きましたが、これで「へーダメなんだ」とか「そりゃそうでしょ」で終わって欲しくない思いもあります。私が苦労したことを他の人にもやって欲しいとかいうわけではなく、実際やることを通しての気づきというか、理解の深さが変わるので。実際挑んだら説明できるようにはなる、少なくともその材料は揃う。説明も結論も私と違って全然いいのです。

この手の「実際やってみた方がいい」と言う話は、実践から得たものを形式知にしきれていないと言うことなんだけど、まあそもそも共同化できてないから表出化も難しいのだよとか自己擁護しておく。

結論

コレクションとしてこだわりがない場合は List で扱っています。 何かの原則とかに沿ったキレイなものではなく、単に現実的な使い勝手による選択です。

冒頭で触れた StringCharSequence だと String を選ぶわけですが、その説明もだいたいそんな感じです。

Java言語としては適切な型があって欲しいとは思うのですが、サードパーティのコレクションライブラリを使ってみて感じるのは、置き換える労力をかけるところでもないと言うことです。コレクションはコレクションだけで完結せず、他のライブラリやフレームワークに引き渡すインタフェース(文字通りの)となっているため、サードパーティのコレクションライブラリをうまく使うのは難しく、労力に見合わないこともしばしばです。

そう言う意味でCollectionsFrameworkは返済できない負債となってしまっているのですが、List を使っていて不利益を被ることはほとんどないのが実状。 学び方によっては苦労しちゃうんだろうなぁ、それは残念なところかなぁ、とは思います。 まあCollectionsFrameworkなんてJava1.2時代のもの、ツッコミどころなんてない方がおかしい。にも関わらずいまだに使えてるのって凄いなぁ(ただの感想

蛇足

ところで9年前に

「List list = new ArrayList();」を定型句として扱うなんて、もってのほかです。

とか言ってた。なんか機嫌悪かったんですかね。