日々常々

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

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 とか)と連携しているかを把握できれば、ここのエントリで書いたように楽勝です。……それがハードル高いんだってーの。

SpringBootとJSPと

JSPはすごくいい技術なんだけど、使い所も減ってきて最近はあまり見なくなってきました。

Spring Boot(2.5.2)のドキュメントを見てるとこんな感じ。

7.7.1. The “Spring Web MVC Framework” / Template Engines

Spring MVC supports a variety of templating technologies, including Thymeleaf, FreeMarker, and JSPs.

「サポートしてるよ!」と言いつつ一番最後。

If possible, JSPs should be avoided.

悪いことは言わないから避けた方がいいよ?ってさ。

7.7.4. Embedded Servlet Container Support / JSP Limitations

With Jetty and Tomcat, it should work if you use war packaging.

JettyやTomcatならwarにしたら多分動くよ、とのこと。なんとなく「JettyやTomcatが動かすはず。知らんけど。」くらいの印象を受けました。「知らんけど」よりもうちょっと責任感ありそうだけど、動かすのは自分じゃないって主張してる感ある。

「Spring Bootのつもりで作っててもたまに動かない制限ある」って感じだし、やっぱSpringBoot+JSPの新規採用は避けるのが無難。まぁ新規でわざわざJSPにしたがるのは多分ないでしょうけど、この辺り検討するのは既存のJSPを引き継いだり拡張したりって文脈だろうし、「使うな」なんて言っても現実見ようねって話になりがち。使わない方向に押し切った方がいいことも多いだろけど、いちがいにはいえない、ようはばらんす。バランスなんで天秤に載せる錘にどんなものがあるかは知っておきたいし、どれを使うかは現場で見極めていきたいところ。まぁJSP自体は悪い子じゃないんだよ。。。

えいご

英語のニュアンス、いまいちよくわからないんだけど、英英辞書ひいたりコアイメージ見たら「あーね」ってなることもよくあります。そいや最近図解してくれてるのを買いました。

多分電子より物理の方がいいです。手頃なサイズだし。説明も私みたいな雰囲気人間にはちょうどよくて、shouldはこんな感じ。

f:id:irof:20210629124700p:plain

めっちゃ弱いんよなぁ。学生時代shouldは強制くらいに覚えた記憶あるんだけど、若干弱いのを知ったときは混乱したりも。shouldとかこの辺の使い分けは RFCのも役に立ちますね。英語ドキュメント読むときはほぼ必須。。

さーばーさいどれんだりんぐ

Reactとかフロントはフロントで作ってAPIでごにょごにょってのが多分今は優勢で、JSPのようなサーバーサイドでHTML書き出すのは若干古いアプローチと捉えられたりするかもしれませんが、これも結局使い所なんだよなぁ、と思ったり。 UIのリッチさとかセキュリティとか性能とか、いろんなパラメータがあるんで、その辺を見ながら丁寧に仕事していきたいと思ってる。

どうでもいいけどReact5回くらい完全に理解したのに、未だまったく理解できない。困ったもんだ。。。

蛇足

そいやJakarta Server Pagesを「JSP」って呼び続けていいのかはよくわからないですが、ダメと言われたところでどうせそう呼ばれるんだろうなぁ、って。

年中税金的なものを払ってる気がする

多いと思ったら1年の12ヶ月中6ヶ月は何かしら払ってるんですね。打率5割。そりゃ多いわ。

  • 4月
  • 5月
    • 所得税。所得に応じて。確定申告の締め切り延期でちょっとズレてる。
    • 消費税。売上の10%あればいい。確定申告の締め切り延期でちょっとズレてる。
  • 6月
    • 住民税。所得の1/10あったら多分足りる。タイミングは住んでる場所による。
    • 国民健康保険料。80万円超えてきた。
  • 7月
    • 所得税の予定納税。5月と同じくらいと思っていればまぁ。
  • 8月
    • 個人事業税。8月と11月だけど忘れるから2回分まとめて。所得の5%?
  • 11月
    • 所得税の予定納税。5月と同じくらいと思っていればまぁ。

なんか微妙に計算合わない気がするけど、まぁこんなもんでしょう…… 固定資産税とかないから、その辺ある人はもう少し増える?(よくわかってない

個人事業主なんで税金的なものを自分で払う必要があり、引き落とされたり振り込んだりする時に口座にお金がないとだめなんですよね。必要なタイミングでお金をおいとかないと振替失敗とか考えるだけでも面倒そうです。 いついくら持っていかれるか把握してないので迂闊に口座を空にできず、そこそこの額を遊ばせてるわけでして。 なんでいい加減整理してみよう、と思い立ってのこれです。

住民税と国民健康保険料は自動振替してない、かつ金額的にコンビニとかキャッシュレスとか使えないので銀行に行かねばならなくて。 6月は同じなんだけど用紙届くタイミングが微妙に違って、去年とか同じ月に2回銀行いくことになってとても面倒くさかった。

全部まとめて一回で持ってってくれないものかなぁ。。