以前SpringBoot2.2の移行検証作業をした。
jis.hatenablog.comそしてお仕事で正式に移行することになった。
検証したときは2.2だったが現在は2.3になっていた。まあそんなに違いはないだろう。移行検証済だから楽勝さ……と思っていたら色々とハマったことがあった。2.3になり微妙に挙動が変わっていたらしい。
そのハマったことをまとめておく。
- 前提
- 事例1 validation関連のアノテーションがすべてコンパイルエラーとなった
- 事例2 filterが適用されない処理があった
- 事例3 一部の単体試験でmockが利用できなかった
- 事例4 SpringBootのログがファイル出力されない
- 事例5 healthチェックが利用できない
前提
移行前の状態は前回の移行検証と同じだ。また移行に必要な作業も基本的には同じ。jettyのバージョンアップ必要(9.4.9以上)なのも変わりはない。
ただ、検証時とは異なりSpringBoot2.3はセントラルレポジトリに登録されているので個別にレポジトリ指定をする必要はない。
事例1 validation関連のアノテーションがすべてコンパイルエラーとなった
概要
SpringBoot2.3に切り替えたところアノテーションによるバリデーション(@NotNull)などがすべて利用不可になっていた。具体的にはjavax.validation.*が利用できなくなった。
原因
SpringBoot1.5ではjavax.validation.*はhibernate-validatorにおいて依存先に指定しているあるライブラリで定義されおり、hibernate-validator自体はspring-boot-starter-webで依存先ライブラリとして指定されていた。
だがSpringBoot2.3になってspring-boot-starter-webからhibernate-validatorの依存がなくなり、結果としてjavax.validation.*も利用不可となった。
対処
新たに依存先としてspring-boot-starter-validationを追加する。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
なぜ前回の検証では問題なかったのか。それはSpringBoot2.2においてもspring-boot-starter-validationは登場していたようなのだがspring-boot-starter-webでこれを依存先と指定していた。そのため今回のような問題にはならなかったようだ。
事例2 filterが適用されない処理があった
概要
各アプリケーションではリクエストを受け付けたときとそれを返却するときにログ出力をしている(リクエストログ)。
このリクエストログ出力処理はfilterで実施している。
SpringBoot2.3に切り替えた後このリクエストログが出力されないことがあった。
具体的にはリクエストの処理途中でエラーが発生したときにそのリクエストの返却ログ出力がされなくなった。
原因
SpringBootではリクエスト処理の途中でエラー(例外)が発生したときErrorControllerへとforwardする。
SpringBoot1.5ではforwardされた先もfilter処理の適用対象となっていたため返却時のリクエストログ出力はできていた。
だがSpringBoot2.3ではデフォルトの定義だとfilterが適用されるdispatcherTypeがREQUESTのみになった。
forwardされたリクエストのdispatcherTypeはFORWARDとなるためfilter処理の適用対象外となってしまう。
対処
リクエストログを出力するfliter(LoggingFillter)は@Componentを付けてSpring上に自動登録しているがこれだと処理対象のdispatcherTypeはデフォルト定義になる。つまりREQUESTのみとなる。
そのため@Component指定をやめて@Configuration指定したクラスからFilterRegistrationBeanでLoggingFilterを登録するように変更。そしてFilterRegistrationBeanの作成時に適用対象のdispatcherTypeとしてREQUESTとFORWARDを指定するように変更。
@Bean
public FilterRegistrationBean<LoggingFilter> loggingFilter() {
FilterRegistrationBean<LoggingFilter> bean = new FilterRegistrationBean<LoggingFilter>();
bean.setFilter(new LoggingFilter());
bean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);
return bean;
}
事例3 一部の単体試験でmockが利用できなかった
概要
以下のような構成をとるクラスの単体試験においてmock化した対象ではなく本物が呼び出された。
mapperA:テーブルAにアクセスするためのインタフェース。実装クラスはmyBatisとSpringBootが自動生成する。
サービスB:mapperAを保持するクラス。コンストラクタ経由で注入する。mapperAのgetterを用意。mapperは数が多いため利用するサービスは直接mapperを注入するのではなくサービスBのようなものを経由して利用している。
サービスC:テスト対象のクラス。サービスBをコンストラクタ経由で注入する。mapperAはサービスB経由で利用。
単体試験はmapperAで通常発生しないような例外(トランザクションエラーなど)を発生させてそのときの挙動を確認するというものである。
mapperAを@MockBean指定しMock化して単体試験をおこなうのだがこのMockが呼び出されず本物が呼び出されてしまった。
原因
mapperのMock化ができなくなったわけではない。だが単体試験時にサービスBに注入されなくなってしまった。サービスBではmapperAのsetterは設けていないため単体試験で無理やり設定することもできない。
インタフェースをMock化した単体試験は他には存在するが問題なく試験できていた。
mapperをMock化したときのみ今回の事象が発生した。
フレームワークが実装クラスを自動生成するようなものがだめなのだろうか? 原因は不明。
対処
サービスBを@SpyBean指定しmapperAを返す処理をmock化(@MockBean指定したmapperAを返す)することで想定通りの単体試験を実施できた。
事例4 SpringBootのログがファイル出力されない
概要
SpringBoot2.3に切り替えた後、SpringBootのログファイルにログが出力されなくなった。コンソールには出力されていた。またアプリケーションのログは通情通り出力されていた。
原因
ログファイル出力場所指定のプロパティが変更になっており従来のものはdeprecateになっていた。
対処
ログファイルの出力場所指定をlogging.fileからlogging.file.nameに変更。
なおlogging.fileプロパティはSpring Yaml Properties Editorでファイルを開いても警告マークがでないのでかなり厄介だ。deprecateなことはマウスカーソル指定でわかるのだが……。
事例5 healthチェックが利用できない
概要
他のプロジェクトでは利用可能になっているhealthチェックが特定のプロジェクトだけ利用できない。
原因
指定対象のプロパティが間違っていた。正確には足りなかった。
healthチェックはデフォルトでは/actuator/healthがエンドポイントとなる。
利用不可のプロジェクトではこれを/management/healthに、他のプロジェクトでは/healthとしてマッピングしていた。
具体的には利用不可のプロジェクトではhealthチェックのエンドポイントをmanagement.endpoints.web.path-mapping.health=/management/healthで指定し、他のプロジェクトではmanagement.endpoints.web.base-path=/で指定している。
だがmanagement.endpoints.web.path-mapping.*はactuatorが提供する機能のエンドポイントのみを変える(例えばhealthをcheckに変えたいなど)ようである。
このため起点となるエンドポイントは/actuatorのままだった。起点を変えたい場合はmanagement.endpoints.web.base-pathで指定する必要がある。
SpringBoot2.2ではこの指定で問題なかったためSpringBoot2.3でどうやら変わったようだ。
対処
management.endpoints.web.base-path=/managementに内容を変更。healthチェックの名称は変更不要のためpath-mappingの指定は削除した。