日々常々

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

SpringBootでRestTemplateの設定を変えたい

  • Spring Boot 2.5.2

本稿の対象

以下のような人が対象です。

  • SpringBootを使っていて new RestTemplate() とかを書いた/見たことがある。
  • SpringBootを使っていて new RestTemplateBuilder() とかを書いた/見たことがある。
  • ぼんやり知ってるけど仕組みとかおさえておきたい。
  • SpringBootのドキュメント/コードの読み方の一例を知りたい。

特に new してるのを書いたり見たことある人に読んでほしい。悲しい事故が起こる前に。

以下のような人は対象ではありません。

  • SpringBootのドキュメントとコードを必要な時に読んでいる。
  • RestTemplate を使っているがSpringBootを使っていない。
  • SpringBootでRestTemplate を使っているが、 restTemplateBuilder.build() しか書いた/見たことがない。

SpringFrameworkはドキュメント全体的にしっかりしているんだけど、コードとあわせて読まないといまいち意味がわからなかったりします(私は)。それでいいんだっけ?とか不安になっちゃうし、「なんでこれで動くん?」みたいに疑心暗鬼になることもしばしば。ということで「 RestTemplate の設定の仕方」を題材にコードのこの辺見てますよ、みたいなことを書こうと思いました。何気に主題はこちらだったりします。

本文

公式ドキュメントに RestTemplate Customization ってストレートな項目があるんで、これ読みましょう。

……で終わるならいいんですが、書いてる内容を咀嚼しながら適当なこと書きますね。

RestTemplatespring-web に入ってる大昔(SpringFramework 3.0らしい)からあるとても便利なクラスです。 SpringFramework 5.0からメンテナンスモードに入り、ノンブロッキングの流れも受けて WebClient に徐々にうつっていくとは思いますが、現時点では 決して非推奨ではありません@Deprecated とかもついてない。たまに「もう使ってはいけない」とかいう話を聞いたりするので強調しておきます。 というか、まだ WebClient だと厳しいことにも引っかかったりするんで、WebFluxを使ってるとか必要性があったり、チャレンジして何か踏んでも何とかできる方でもなければ RestTemplate を使うのが無難です。そんな安定志向な私。他の人がどう判断するかは知らない。

さて本題の RestTemplate の設定変更ですが、ドキュメントで示されているのは以下。

「なるべく狭い範囲でカスタマイズしましょう」

共通の設定は RestTemplate 全てに影響しちゃうから、「ほんとに全部?特定の通信先に固有の設定をしたいんじゃないの?」みたいな感じですかね。

大前提として

SpringBootを使ってる文脈で new RestTemplate() とかするのは基本的に無しです。1年ちょい前にmakingさんがツイートしてたのに続けてはなしてたこと の説明が本稿な感じです。

newしちゃうと「SpringBootだからこれでいけるだろー」みたいなのが軒並み通らなくなって、よくわからないことになります。RestTemplateインスタンスRestTemplateBuilderbuild して作るものです。その RestTemplateBuilder も自分で new するんじゃなく、SpringBootが管理してるインスタンスを貰ってくるものです。基本から外れるものもたまにあるけど、それはそれ。

ということで、基本のお話だー

まず build の直前

最初に検討するのは RestTemplateBuilderbuild する箇所。

要するにコンストラクタで RestTemplateBuilder をインジェクションした後ですね。これでやってる人が多いんじゃないかな? RestTemplateBuilder の何かしらのメソッド呼ぶたびに新しい RestTemplateBuilderインスタンスが返されるから他のRestTemplateに影響しない。安全。

コードを見たら 、thisじゃなく新しいインスタンスがreturnされてるのがわかりますね。

次に RestTemplateCustomizer

RestTemplateCustomizer を実装してBean登録する。 RestTemplateBuilderのフィールドに(RestTemplateBuilderConfigurer 経由で)インジェクションされて、build するときに勝手に呼ばれる。 なのですべてのRestTemplateBuilderから作るRestTemplateに必ず適用される感じ。

なんとなく RestTemplateCustomizer はあまり知られてない気がする。ライブラリとかでは結構使われるんですが。 RestTemplateCustomizerって単純にRestTemplateを受け取るだけのConsumerなFunctionalInterfaceなんですよね。

@FunctionalInterface
public interface RestTemplateCustomizer {

    /**
    * Callback to customize a {@link RestTemplate} instance.
    * @param restTemplate the template to customize
    */
    void customize(RestTemplate restTemplate);
}

GitHub: spring-projects/spring-boot/.../RestTemplateCustomizer.java

実務だと「RestTemplate を受け取って設定を行う共通のユーティリティメソッドを忘れずに呼び出す」とか規約作るくらいならこれBean登録しとこう、とかかなー。

RestTemplateCustomizerは名前の示す通りRestTemplate が対象の拡張ポイントです。 RestTemplateBuilder に対してではないって把握しておかないと「あれ、これでできないの?」とかなったり、たまにある。ということでRestTemplateBuilder に共通的に何かを行いたい時には使えません。次の手段で。

最後の手段として RestTemplateBuilder 自体をBean登録

RestTemplateBuilder はSpringBoot使っているならおなじみ(?)の @ConditionalOnMissingBean な「何もしなければ spring-boot-autoconfigure が勝手に作成してくれるもの」です。定義はこう。

@Bean
@Lazy
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
    RestTemplateBuilder builder = new RestTemplateBuilder();
    return restTemplateBuilderConfigurer.configure(builder);
}

GitHub: spring-projects/spring-boot-autoconfigure/.../RestTemplateAutoConfiguration.java

@ConditionalOnMissingBean は名前の通り「この型のBeanが登録されていない場合のみ有効になる」ものなので、自分でRestTemplateBuilderを登録したらこれが使われません。 つまり 迂闊にやるとSpringBootの制御を外れた RestTemplateBuilder を作ってしまう ことになります。 ドキュメントにも「Finally」と最後に挙げてるくらい、やって欲しくなさげ。

で、迂闊じゃないやり方は元と同じように RestTemplateBuilderConfigurer を使用することです。ドキュメントにも書いてる。これを使わないと以下のような問題が起こります。

まず RestTemplateCustomizer がBean登録されてても効きません。

例えば @AutoConfigureMockRestServiceServerRestTemplate にバインドされす、

Unable to use auto-configured MockRestServiceServer since MockServerRestTemplateCustomizer has not been bound to a RestTemplate java.lang.IllegalStateException: Unable to use auto-configured MockRestServiceServer since MockServerRestTemplateCustomizer has not been bound to a RestTemplate

とか顔を真っ赤にして(ログが赤い的な意味で)怒られてしまいます。エラーログに丁寧に書いてくれるのほんとありがたいんだけど、早口な英語で捲し立てられてる気分でちょっとビクッとしちゃったりしますよね。スタックトレース怖い。こわくないよ。 これ MockServerRestTemplateCustomizer で実現されてるからですね。

あと TraceRestTemplateCustomizer とかも効かなくなるので、せっかく spring-cloud-sleuth とか入れてても分散トレーシングが途切れます。一番大事なとこで。。。

他にもHttpMessageConverters も入らないからJacksonの spring.jackson.* とか設定してても効かなくなる。「あれー application.yml にちゃんと書いてるのになー」とかなる、くらいならいいんだけど(よくないけど)、メッセージ変換周りがまるごと spring-boot じゃなく spring-web の素 RestTemplate になってしまう。結構違いあるんで注意なとこです。

さらに RestTemplateRequestCustomizer も入らない。実装したことないし、心当たりある例も思いつかないけど。

今のところ RestTemplateBuilderConfigurer はこの3種類しか扱っていませんが、今後バージョンアップで増えたり変わったりするかもしれない。となると「バージョンあげたらうまく動かなくなった」とかの禍根になります。JavaやSpringFrameworkの互換維持ってかなりすごいんだけど、こういうの積み重ねちゃうとバージョンアップの手を緩めさせてしまいます。これが怖い。

やるべきことはシンプルで、元のと同じように RestTemplateBuilderインスタンスを作った後に RestTemplateBuilderConfigurer#configure(RestTemplateBuilder) を被せてあげるだけ。

@ConditionalOnMissingBean とかの spring-boot-autoconfigure のBeanを上書きするときは「だいたい元の実装を真似る」ってやっとくのが無難です。ドキュメントに書いてはいるんだけど、それのやってることを文章だけで読み取るのは結構難度高いと思う。

この「最後に」とかで挙げられてる RestTemplateBuilder のBean登録をいつ使うかっていうと、アプリケーション全体で RestTemplate じゃなく RestTemplateBuilder で設定したいときです。たとえばタイムアウトの設定とかは RestTemplate 経由だと RequestFactory(の実装クラス)をいじらないといけないけど、RestTemplateBuilder だと直接的なメソッドがあったりするんですよね。

irof.hateblo.jp

前に書いたこれとか。だけどタイムアウトって通信相手ごとに設定する気もするし、コンストラクタのbuild直前、つまり最初に挙げられてた選択肢でいい気もしなくはない。 RequestFactory もいじるならこっちかなーって思ってる。 RestTemplateRequestCustomizer でも手が届くけど、一旦作ったのを上書きって感じになるし、なんとなく無駄に感じるので……気分の問題だけど。

まぁここで書いたような事故もあるし、RestTemplateRequestCustomizer で事足りるなら RestTemplateRequestCustomizer でやっとく方が安全でしょね。という意味で、三つの方法の中では最後になってるのかなと。

most extreme (and rarely used) option

最後の最後に書いてる。

The most extreme (and rarely used) option is to create your own RestTemplateBuilder bean without using a configurer.

一応 RestTemplateCustomizer 使わないって選択肢も挙げられてるけど、「何でそんなことすんの・・・?」みたいな感じを受けますね。 SpringBootから RestTemplateBuilder を切り離したいなら……でもそれなら RestTemplateBuilder を使わずに new RestTemplate() でいい気がするけども。 自分の制御下にない RestTemplateBuilder を使っているコードに影響を与えたいなら、かなぁ……。

でも「RestTemplateを設定したい」と言いつつ

実際のとこ RestTemplate の設定をしたいと言いつつ、 MessageConverter を設定したいことの方が多い気がします。 そういう時に下手にこの辺に書いてあるように RestTemplateBuilderRestTemplateCustomizerMessageConverter を丸ごと入れ替えたりすると、先にあげたような「Jacksonの設定が効かない!」とか、なりがちです。

設定を変えたいものはどのライブラリの管轄で、それがどういう仕組みで直接使うAPIRestTemplate とか)と連携しているかを把握できれば、ここのエントリで書いたように楽勝です。……それがハードル高いんだってーの。