Spring Boot 1.5系から2.1系へのマイグレーションガイド
はじめに
あるWebサービスで利用していたSpring Bootを、サービスリリース時の1.5.4.RELEASEから(2019年5月時点の最新版である)2.1.4.RELEASEまでバージョンアップした際の知見集です。
バージョンアップ対応は昨年の5月頃に完了し、完了時点での知見を社内向けのesaにまとめていたのですが、こちらはそれをベースに公開用に編集したものになります。
(グラフは某社の決算発表をイメージして、バージョンアップのインパクトを視覚的に伝えるために作成したネタです)
弊プロダクトにおいては、Lombokで横着していた箇所の宣言順による問題と、Mockitoの挙動変更に伴うテストの修正が大きめの印象でした。
メトリクスの収集にPrometheusを使用していたのですが、Spring Boot 2系から標準になったMicrometerへの切り替えは挙動を理解しながら修正していくコストが少々大きかったですが、慣れると非常に便利で分かりやすいと感じました。
また、一部のサブシステムにおいてapplication.ymlを思いっきりキャメルケースで書いていたため、ケバブケースへの書き換えに伴って@ConfigurationProperties
などを利用して直接YAMLのキーを指定して値を読み出している箇所の修正も必要でした。
実装周り
Lombokアノテーションの仕様変更
Lombok v1.16.22のCHANGELOGによると、
FEATURE: Private no-args constructor for @Data and @Value to enable deserialization frameworks (like Jackson) to operate out-of-the-box.
Use lombok.noArgsConstructor.extraPrivate = false to disable this behavior.
簡単に言うと「@Data
や@Value
アノテーションを付与すると引数なしの(デフォルト)コンストラクタがprivateで宣言されるのがデフォルトの挙動になったよ」とのこと。
「lombok.propertiesにlombok.noArgsConstructor.extraPrivate = false
って書くとこの挙動が外れるよ」ともあるが、@Data
より先に@NoArgsConstructor
を宣言することで先にpublicなデフォルトコンストラクタが作成されるので、影響範囲が小さかったり、余計なプロパティファイルを書きたくない場合は(今のところは)アノテーションの順番を変えるという解決策をとることもできます(出典)。
ただ、この変更の目的(Jacksonがデシリアライズするときにデフォルトコンストラクタが必要だから、という趣旨)と一致するのであれば、明示的にデフォルトコンストラクタを作らなくてもよくなったということですので、用途や現状のシステムにおけるユースケースによって判断する必要がありそうです。
Netty4ClientHttpRequestFactory
is Deprecated
SpringFramework 5系から廃止されたため、Apache HttpClientを利用することにしました(参考)。
他にもOkHttp3なども利用できるようです。
メトリクスがMicrometer経由になった
弊プロダクトではio.prometheus
のsimpleclient-spring-boot
を使用してPrometheusフォーマットによるメトリクスの出力を行っていたのですが、これをSpring Boot 2系の標準メトリクスライブラリであるMicrometerに切り替える必要がありました。
ちょっとサンプルは書きづらいのですが、基本的にはMicrometerのセットアップ(どのメトリクス製品を使うか)と、GaugeやCounterなどを読み替えて適宜用途に合うように修正していきます。
// io.prometheus でのGaugeの作り方の例
Gauge gauge = Gauge.build()
.name("health_dsl_context")
.help("Health dsl context")
.register();
// io.micrometer でのGaugeの作り方の例
@Autowired /* 何らかの方法でBeanをInjectしてください */
private PrometheusMeterRegistry prometheusMeterRegistry;
AtomicDouble newGauge = prometheusMeterRegistry.gauge("health_dsl_context", new AtomicDouble(0.0));
@ConfigurationProperties
のプレフィックス指定は小文字のケバブケースで書かないと例外を吐くよ
application.yml
に記述したプロパティを @ConfigurationProperties
によって読み出すことがあるかと思いますが、Spring Boot 2.0以降から参照する時にキャメルケース以外のケースで書くと InvalidConfigurationPropertyNameException
を吐くようになりました。
リファレンス によると、
The prefix value for the annotation must be in kebab case (lowercase and separated by -, such as acme.my-project.person).
とのことで、YAMLファイルのほうは(上述の通り)キャメルケースなどで書いても問題ないのですが、コード側からの読み出し時に上記のような制約をかけられている以上、ケースを混ぜ書きしていると設定値の可読性が下がりそうなので、小文字ケバブケースでの記述に揃えることにしました。
allow-bean-definition-overriding
は明示的に指定する
標準ではBeanのオーバーライドが無効になりました。
利用している場合は application.yml
に spring.main.allow-bean-definition-overriding=true
指定を追加します。
(テストで使っているだけ、といった場合は環境プロファイルなどで切り替えるとよいかもしれません)
MySQLのConnector/JとjOOQのパッケージクラス変更
Connector/Jのバージョンアップに伴い、com.mysql.jdbc.Driver
-> com.mysql.cj.jdbc.Driver
に変更します(パッケージだけではなくドライバ指定もすべて)。
また、jOOQのコードジェネレータ設定は以下のように変更します。
org.jooq.util.JavaGenerator -> org.jooq.codegen.JavaGenerator
org.jooq.util.mysql.MySQLDatabase -> org.jooq.meta.mysql.MySQLDatabase
Jacksonでシリアライズするときのタイムスタンプフォーマット指定
application.yml
で spring.jackson.datetime="yyyy-MM-dd'T'HH:mm:ss.SSSX"
を指定しました。
これを書かないと、タイムゾーン識別子が +0000
のような形式になります。
SpringBootApplicationBuilderの起動モード
リアクティブモードが増えたことでEnumによる指定に切り替わったため .web(WebApplicationType.(SERVLET|NONE|REACTIVE))
といった形で書き直します。
public static void main(String... args) {
new SpringApplicationBuilder()
.web(WebApplicationType.NONE)
.sources(ExampleConfiguration.class)
.main(ExampleApp.class)
.build()
.run(args);
}
RequestInterceptorでHandlerMethodを受け取る場合
ちゃんとinstanceofで型チェックを入れないと死にます(むしろ、今までよく動いてたな…)。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler instanceof HandlerMethod) {
/* handlerを扱う処理 */
}
}
Content-type: multipart/form-data;boundary=
で完全一致のテストケースを書かない
小ネタですが、boudary=
の前にencoding=UTF-8;
が入るようになったため、完全一致を成立条件にしていたテストが落ちました。
テストケースの設定も悩みどころではありますが、containsStringで比較する等、ある程度柔軟に受け入れるようなケースを書いてあげるほうがよいでしょう。
@RestController
を付与したコントローラーで返り値をString型にしているときのContent-typeが text/plain
になりました
以前は(おそらく明示的な指定がない限り)application/json
で返っていたため、この修正によってクライアントがレスポンスをパースできなくなるといった問題が発生しました。
文字列だけのJSONも構造としては許容されるので今までの挙動でも問題はなかったと思いますが、思わぬハマりポイントでした。
回避策としては、Content-type: application/json
で返すことを正とすると、JacksonのTextNodeとして返してあげることで解決できます。
(参考にしたStackOverflowの回答)
@GetMapping("/v1/test")
public TextNode returnStringOnlyJson() {
return new TextNode("responseText");
}
RestTemplateBuilder
のタイムアウト指定はDuration
による指定になりました
今まではミリ秒をlong型で指定する形だったので、下記のように書き直しました。
private RestTemplateBuilder restTemplateBuilder(int connectTimeout, int readTimeout) {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.ofMillis(connectTimeout))
.setReadTimeout(Duration.ofMillis(readTimeout));
}
テスト周り
MockitoのanyObject()
やanyListOf()
はDeprecatedになりました。
anyObject()
やanyListOf()
はDeprecatedになりました。any()
や anyList()
、any(HogeHoge.class)
を使いましょう。
MockitoのanyInteger()
などはそのまま使うとnull非許容になりました。
nullable(HogeHoge.class)
を使いましょう。
Author And Source
この問題について(Spring Boot 1.5系から2.1系へのマイグレーションガイド), 我々は、より多くの情報をここで見つけました https://qiita.com/Plemling138/items/0d2297c080e53e782f85著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .