OpenRewriteを使って SpringBoot Upgrade 2.7 to 3.2
まえがき
ようやく上げれるタイミングが来ました🎉
2023/12/21 現在、3.2 でしたので、発生した事象に対応した内容を残しておきます。
今はOpenRewriteなる、アップグレードの自動化をしてくれるやつがいるんですね。
このツールと、マイグレーションガイドに沿ってやっていきます。
作業のお供には (Bing) Copilot 🤖くんが協力してくれてます!
※GitHub Copilot Chat くんは SpringBoot 3はまだリリースされていないって言い張るので。。
環境
WSL2 Ubuntu 22.04
$ java --version
openjdk 17.0.8 2023-07-18
OpenJDK Runtime Environment Temurin-17.0.8+7 (build 17.0.8+7)
OpenJDK 64-Bit Server VM Temurin-17.0.8+7 (build 17.0.8+7, mixed mode, sharing)
$ gradle -version
------------------------------------------------------------
Gradle 8.3
------------------------------------------------------------
Build time: 2023-08-17 07:06:47 UTC
Revision: 8afbf24b469158b714b36e84c6f4d4976c86fcd5
Kotlin: 1.9.0
Groovy: 3.0.17
Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM: 17.0.8 (Eclipse Adoptium 17.0.8+7)
OS: Linux 5.15.133.1-microsoft-standard-WSL2 amd64
OpenRewrite を導入する
1. build.gradle に追記します。
plugins {
id("org.openrewrite.rewrite") version("6.6.1")
}
rewrite {
activeRecipe("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2")
}
repositories {
mavenCentral()
}
dependencies {
rewrite("org.openrewrite.recipe:rewrite-spring:5.1.6")
}
2. コマンド実行
gradle rewriteRun コマンドを実行します。
$ ./gradlew rewriteRun
実行には少し時間がかかります。
終わると、以下のような変更結果がコンソールに出力されます。
> Task :rewriteRun
Validating active recipes
Scanning sources in project :
Using active styles []
All sources parsed, running active recipes: org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2
Changes have been made to src/main/java/jp/app/entity/Fruit.java by:
org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2
org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1
org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta
org.openrewrite.java.migrate.jakarta.JavaxPersistenceToJakartaPersistence
org.openrewrite.java.ChangePackage: {oldPackageName=javax.persistence, newPackageName=jakarta.persistence, recursive=true}
これは javax.persistence を jakarta.persistence にパッケージ変更した、という内容ですね。
全部見ていくとつらいので、レシピで対応したけど、エラーになったものを見ていきます。
OpenRewriteした後の対応
追加された org.glassfish.jaxb:jaxb-runtime の記述削除
build.gradle に org.glassfish.jaxb:jaxb-runtime が追加されました。
JakartaEE8用の対応っぽいです。が、gradle が ビルドエラーをずっと出し続けます。
Dependencies can not be declared against the testCompileClasspath configuration.
ちょっと胡散臭いので消すと、ビルドエラーが解消されました。
以下消した対象です。多い。
dependencies {
productionRuntimeClasspath "org.glassfish.jaxb:jaxb-runtime:2.3.9"
testCompileClasspath "org.glassfish.jaxb:jaxb-runtime:2.3.9"
compileClasspath "org.glassfish.jaxb:jaxb-runtime:2.3.9"
rewrite "org.glassfish.jaxb:jaxb-runtime:2.3.9"
testRuntimeClasspath "org.glassfish.jaxb:jaxb-runtime:2.3.9"
rewriteimplementation "org.glassfish.jaxb:jaxb-runtime:2.3.9"
rewritetestImplementation "org.glassfish.jaxb:jaxb-runtime:2.3.9"
runtimeClasspath "org.glassfish.jaxb:jaxb-runtime:4.0.4"
}
ちなみに、SpringBootの依存関係に含まれています。
2023/12/22 現在、jaxb-runtime:4.0.4 ですね。
追加された jakarta系の依存関係の削除
jaxb-runtime みたいにエラーになるわけではないんですが、jakarta系の以下の依存も追加されます。バージョン固定して、移行しやすくするためかな?
こちらも余計なお世話なので、今回はさよならします。
implementation "jakarta.persistence:jakarta.persistence-api:3.1.0"
implementation "jakarta.transaction:jakarta.transaction-api:2.0.1"
implementation "jakarta.validation:jakarta.validation-api:3.0.2"
起動時エラー 循環参照
[2023/12/22 12:41:28][W][restartedMain][AbstractApplicationContext:624] Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Unsatisfied dependency expressed through method 'dataSourceScriptDatabaseInitializer' parameter 0: Error creating bean with name 'dataSourceScriptDatabaseInitializer': Requested bean is currently in creation: Is there an unresolvable circular reference?
[2023/12/22 12:41:28][E][restartedMain][LoggingFailureAnalysisReporter:40]
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌──->──┐
| dataSourceScriptDatabaseInitializer defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]
└──<-──┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
色々悪戦苦闘した結果、@DependsOnDatabaseInitialization
をコメントアウトすることによって、ようやくエラーログが出ました。
@Bean
// @DependsOnDatabaseInitialization <- OpenRewrite がつけたこのアノテーションをコメントアウト
public HikariDataSource createPostgresDataSource() {
return postgresDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
Suppressed: java.lang.ClassNotFoundException: org.hibernate.dialect.PostgreSQL95Dialect
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at org.hibernate.boot.registry.classloading.internal.AggregatedClassLoader.findClass(AggregatedClassLoader.java:206)
... 52 common frames omitted
PostgreSQL95Dialect がなくなったっぽいです。
やってる環境だとJavaで書いていたので、以下のように変更します。
- properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL95Dialect");
+ properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
もしくは、PostgreSQLDialectがデフォルトで選択されるようなので、接続するPostgreSQLのバージョンが新しいものであれば、消して良いそうです。
[2023/12/22 19:44:25][W][restartedMain][DialectFactoryImpl:152] HHH90000025: PostgreSQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
jakarta.servlet.ServletException: Request processing failed: java.lang.IllegalArgumentException: Name for argument of type [java.lang.Integer] not specified, and parameter name information not found in class file either
@RequestParam("id") のnameはちゃんと指定しないといけなくなったみたいです。
- public ModelAndView edit(ModelAndView mv, @RequestParam Integer id) {
+ public ModelAndView edit(ModelAndView mv, @RequestParam("id") Integer id) {
@PathVariable
も同様です。
- public ModelAndView update(ModelAndView mv, @PathVariable int id) {
+ public ModelAndView update(ModelAndView mv, @PathVariable("id") int id) {
以下のようなエラーも同様の対処方法になります。
[DirectJDKLog:175] Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalArgumentException: Name for argument of type [java.lang.Integer] not specified, and parameter name information not available via reflection. Ensure that the compiler uses the '-parameters' flag.] with root cause
java.lang.IllegalArgumentException: Name for argument of type [java.lang.Integer] not specified, and parameter name information not available via reflection. Ensure that the compiler uses the '-parameters' flag.
ResponseEntity<String>
is deprecated since version 6.0
The method getStatusCodeValue() from the type responseEntity.getStatusCode().value() に置き換えましょう。
ResponseEntity<String> responseEntity = teamsRestTemplate.exchange(requestEntity, String.class);
- log.info("status: %d, message: %s".formatted(responseEntity.getStatusCodeValue(), responseEntity.getBody()));
+ log.info("status: %d, message: %s".formatted(responseEntity.getStatusCode().value(), responseEntity.getBody()));
The type Base64Utils has been deprecated since version 6.0.5 and marked for removal
org.springframework.util.Base64Utils がなくなるようです。
java.util.Base64 に置き換えましょう
URLの末尾のスラッシュの扱いの変更
As of Spring Framework 6.0, the trailing slash matching configuration option has been deprecated and its default value set to false. This means that previously, the following controller would match both "GET /some/greeting" and "GET /some/greeting/":
これまでは value = {""}
と記述していた場合は、末尾にスラッシュがあってもなくても良しなにしていたのを、厳格にしたようです。
スラッシュ付きにしたい場合は対応が必要です。
- @GetMapping(value = {""})
+ @GetMapping(value = {"/"})
SpringBoot 3.2.0 to 3.2.1 Update
ちょっと起動が遅くなったりして気になったので、調べていたところ、3.2.1で改善されるようなこと書いてたので、3.2.1に上げてみます。
java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
3.2.0の時はエラーにはならなかったですが、3.2.1になってから厳しくなった?みたいです。
ローカルの開発環境なので、allowPublicKeyRetrieval=true を接続プロパティに追加します。
Tomcat 9 -> 10 へ Upgrade
まあ当然なんですけど、ServletのSupportバージョンが変わってるのでそうですよね。
ここでは割愛します(というか、弊環境ではそこまで変更点はなかった。)
ちなみに、Jettyは Servlet 6 をまだサポートできてないので、Servlet5系にダウングレードが必要だそうです。
Jetty does not yet support Servlet 6.0. To use Jetty with Spring Boot 3.0, you will have to downgrade the Servlet API to 5.0. You can use the jakarta-servlet.version property to do so.
その他・気になった点
rewriteimplementation
OpenRewrite を実行したら、build.gradle に rewriteimplementation で jaxb-runtime が追加されました。
rewriteimplementation "org.glassfish.jaxb:jaxb-runtime:2.3.9"
rewritetestImplementation "org.glassfish.jaxb:jaxb-runtime:2.3.9"
見慣れないので、Copilotに聞いてみたところ、
🤖
rewriteImplementation
は、Gradle ビルドスクリプトで使用される依存関係管理プラグインの一つです。このプラグインは、依存関係の解決を最適化するために、プロジェクトの依存関係を自動的に修正します。例えば、プロジェクトが使用しているライブラリのバージョンが古い場合、rewriteImplementation
は自動的に最新バージョンに更新します。このプラグインは、ビルドスクリプトのplugins
ブロックで有効化されます。詳細については、公式ドキュメントを参照してください
とまあ、自動でアップデート作業のサポートをしてくれるもののようです。
アップデートのコマンドが終わったら消しました。
あとがき
SecurityContext 周りも書き換えてくれる OpenRewrite 便利ですね。
ちょっと余計なところもありますが💦
私のプロジェクトで発生した内容だけ記載していますので、ゴリゴリなプロジェクトだとRestTemplateの中身が変わったり、actuator 周りの変更でいろいろあるかと思います。頑張ってください。
SpringBoot は 3.2.1 まで上げると、体感ですが少し挙動のもっさりが改善したように感じます。
それではみなさん頑張ってSpringBootのアップデートに対応していきましょう😇
参考
Discussion