- Spring Boot 2.5.2
本稿の対象
以下のような人が対象です。
- SpringBootを使っていて
new RestTemplate()
とかを書いた/見たことがある。 - SpringBootを使っていて
new RestTemplateBuilder()
とかを書いた/見たことがある。 - ぼんやり知ってるけど仕組みとかおさえておきたい。
- SpringBootのドキュメント/コードの読み方の一例を知りたい。
特に new
してるのを書いたり見たことある人に読んでほしい。悲しい事故が起こる前に。
以下のような人は対象ではありません。
- SpringBootのドキュメントとコードを必要な時に読んでいる。
RestTemplate
を使っているがSpringBootを使っていない。- SpringBootで
RestTemplate
を使っているが、restTemplateBuilder.build()
しか書いた/見たことがない。
SpringFrameworkはドキュメント全体的にしっかりしているんだけど、コードとあわせて読まないといまいち意味がわからなかったりします(私は)。それでいいんだっけ?とか不安になっちゃうし、「なんでこれで動くん?」みたいに疑心暗鬼になることもしばしば。ということで「 RestTemplate
の設定の仕方」を題材にコードのこの辺見てますよ、みたいなことを書こうと思いました。何気に主題はこちらだったりします。
本文
公式ドキュメントに RestTemplate Customization ってストレートな項目があるんで、これ読みましょう。
……で終わるならいいんですが、書いてる内容を咀嚼しながら適当なこと書きますね。
RestTemplate
は spring-web
に入ってる大昔(SpringFramework 3.0らしい)からあるとても便利なクラスです。
SpringFramework 5.0からメンテナンスモードに入り、ノンブロッキングの流れも受けて WebClient
に徐々にうつっていくとは思いますが、現時点では 決して非推奨ではありません。@Deprecated
とかもついてない。たまに「もう使ってはいけない」とかいう話を聞いたりするので強調しておきます。
というか、まだ WebClient
だと厳しいことにも引っかかったりするんで、WebFluxを使ってるとか必要性があったり、チャレンジして何か踏んでも何とかできる方でもなければ RestTemplate
を使うのが無難です。そんな安定志向な私。他の人がどう判断するかは知らない。
さて本題の RestTemplate
の設定変更ですが、ドキュメントで示されているのは以下。
「なるべく狭い範囲でカスタマイズしましょう」
共通の設定は RestTemplate
全てに影響しちゃうから、「ほんとに全部?特定の通信先に固有の設定をしたいんじゃないの?」みたいな感じですかね。
大前提として
SpringBootを使ってる文脈で new RestTemplate()
とかするのは基本的に無しです。1年ちょい前にmakingさんがツイートしてたのに続けてはなしてたこと の説明が本稿な感じです。
new
しちゃうと「SpringBootだからこれでいけるだろー」みたいなのが軒並み通らなくなって、よくわからないことになります。RestTemplate
のインスタンスは RestTemplateBuilder
を build
して作るものです。その RestTemplateBuilder
も自分で new
するんじゃなく、SpringBootが管理してるインスタンスを貰ってくるものです。基本から外れるものもたまにあるけど、それはそれ。
ということで、基本のお話だー
まず build
の直前
最初に検討するのは RestTemplateBuilder
を build
する箇所。
要するにコンストラクタで RestTemplateBuilder
をインジェクションした後ですね。これでやってる人が多いんじゃないかな?
RestTemplateBuilder
の何かしらのメソッド呼ぶたびに新しい RestTemplateBuilder
のインスタンスが返されるから他のRestTemplate
に影響しない。安全。
コードを見たら 、thisじゃなく新しいインスタンスがreturnされてるのがわかりますね。
次に RestTemplateCustomizer
RestTemplateCustomizer
を実装してBean登録する。
RestTemplateBuilder
のフィールドに(RestTemplateBuilderConfigurer
経由で)インジェクションされて、build
するときに勝手に呼ばれる。
なのですべてのRestTemplateBuilder
から作るRestTemplate
に必ず適用される感じ。
- RestTemplateBuilderのcustomizersフィールド
- build直前に呼び出されるconfigureメソッドの適用される箇所。
restTemplate
をreturn
する直前に入ってますね。
なんとなく 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登録されてても効きません。
例えば @AutoConfigureMockRestServiceServer
が RestTemplate
にバインドされす、
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
だと直接的なメソッドがあったりするんですよね。
前に書いたこれとか。だけどタイムアウトって通信相手ごとに設定する気もするし、コンストラクタの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
を設定したいことの方が多い気がします。
そういう時に下手にこの辺に書いてあるように RestTemplateBuilder
や RestTemplateCustomizer
で MessageConverter
を丸ごと入れ替えたりすると、先にあげたような「Jacksonの設定が効かない!」とか、なりがちです。
設定を変えたいものはどのライブラリの管轄で、それがどういう仕組みで直接使うAPI(RestTemplate
とか)と連携しているかを把握できれば、ここのエントリで書いたように楽勝です。……それがハードル高いんだってーの。