Spring Boot 3.0へのバージョンアップで発生した問題点と対応内容

ogp

こんにちは。カート決済部カート決済基盤ブロックの高橋です。

カート決済部では、現在Spring BootのJavaプロジェクトを運用しています。今回Spring Bootのバージョンアップを実施した際に発生した問題点と対応内容、注意点をご紹介します。加えて、使用しているライブラリなどのバージョンも上げているのでご紹介します。

アップデート前後のバージョン

種類 前バージョン 後バージョン
Java 17 17
Spring Boot 2.7 3.0
Gradle 7.x 8.x
SpringFox 3.0.0 -
Springdoc-openapi - 2.1
openapi-generator 5.1 6.5
Spock Framework 2.1-groovy-3.0 2.4-M1-groovy-4.0

JavaはSpring Bootのバージョンアップ前からJava 17を使用しており、今回は変更していません。

Spring Bootのバージョンアップ

今回はSpring Bootの2.7から3.0にバージョンアップしています。

以下の表は公式発表されているSpring BootのバージョンごとのOSSサポート期間です。

Spring Bootのサポート期間

バージョン2.6以前のサポート期間は終了しています。また、2.7や今回バージョンアップした3.0もそれぞれ2023年11月にOSSサポート期間が終了してしまいます。

現在のタイミングですと、3.1のリリースを待って対応でも良かったのですが、以下の理由からこのタイミングで3.0へのバージョンアップを実施しました。

  • 新メンバーが既存プロジェクトに触れる良い機会であったため
  • 3.1への対応をスムーズにするため

javaxからjakartaパッケージに変更

Spring Boot 2.7までは、Spring Framework 5.3がベースでしたが、Spring Boot 3.0では、Spring Framework 6.0をベースとしています。

これにより、Java EEからJakarta EE9へ変更になっているため、パッケージの変更が必要になります。これは、パッケージ名を javax から jakarta に変更することで対応しました。

これと同時にJavaのベースバージョンも17となっているため、これより前のバージョンを使用している場合は、Javaのバージョンアップも同時に行う必要があります。

Spring MVCのURLマッチングの変更

Spring Framework 6.0では、URLの末尾のスラッシュにデフォルトで一致しなくなりました。GET /hoge/GET /hoge は一致しなくなり、以下のようなコードでは、GET /hoge/ はHTTP 404エラーを返すようになります。

@RestController
public class HogeController {

  @GetMapping("/hoge")
  public String hoge() {
    return "Hoge";
  }
}

これらを一致させるためには以下の2つの方法があります。

1つ目は、以下の通り明示的に宣言することです。

@GetMapping("/hoge", "/hoge/")

2つ目は、Spring MVCのWebMvcConfigurerのconfigurePathMatchメソッドをオーバーライドすることで対応します。

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
      configurer.setUseTrailingSlashMatch(true);
    }

}

setUseTrailingSlashMatchメソッドは非推奨になっているのでご注意ください。そのため、修正可能である場合は、呼び出し元のパスを統一する形に修正する方が良いと思います。

アクセスログの対応

Spring Boot 3.0以降では、logback-access-spring-boot-starter が未対応のため、以下のように対応しています。

ライブラリの変更

-   implementation "net.logstash.logback:logstash-logback-encoder:6.6"
-   implementation "net.rakugakibox.spring.boot:logback-access-spring-boot-starter:2.7.1"
+   implementation "net.logstash.logback:logstash-logback-encoder:7.3"
+   implementation "ch.qos.logback:logback-access:1.4.6"
+   implementation "org.codehaus.janino:janino:3.1.9"

アプリケーションクラスに以下のBeanを追加

  @Bean
  public WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>
      webServerFactoryCustomizer() {
    var logbackValve = new LogbackValve();
    logbackValve.setFilename("sample-logback-access.xml");
    return (factory) -> factory.addEngineValves(logbackValve);
  }

ライブラリの変更・バージョンアップ

SpringFoxからSpringdoc-openapiへ変更

Spring Bootのバージョンアップと同時にSpringFoxからSpringdoc-openapiへの移行も行いました。SpringFoxがSpring Boot 3.0では動作しなくなってしまったため、これを機にSpringdoc-openapiへ移行しました。

対応内容は、依存ライブラリの変更です。

-   implementation "io.springfox:springfox-boot-starter:3.0.0"
+   implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0"

これに伴い、Controllerクラスで使用するアノテーションも変更になっています。io.swagger.annotations配下を使用していたものを io.swagger.v3.oas.annotations に変更しています。主なアノテーションの変更点をまとめたのが次の表です。

修正前アノテーション 修正後アノテーション
@Api @Tag
@ApiOperation @Operation
@ApiResponse @ApiResponse
@PostMapping @RequestMapping
@ApiParam @Parameter

実際のコードは以下の通りです。

// 修正前
@Api(value = "Sample", description = "the Sample API")
public class SampleApiController {
    @ApiOperation(
        value = "サンプル取得処理 ",
        nickname = "sampleRequests",
        notes = "サンプル取得処理 ",
        response = SampleResult.class,
        tags={ "sampleRequests", })
   @ApiResponses(value = { 
        @ApiResponse(code = 200,
            message = "200 (OK)",
            response = SampleResult.class),
        @ApiResponse(code = 400,
            message = "400 (Bad Request)",
            response = BadRequest.class)
        @ApiResponse(code = 500,
            message = "500 (Internal Server Error)",
            response = InternalServerError.class)})
    @PostMapping(
        value = "/sample",
        produces = { "application/json" },
        consumes = { "application/json" }
    )
    public ResponseEntity<SampleResult> getSample(
        @ApiParam(value = "" ,required=true ) @Valid @RequestBody GetSampleRequests getSampleRequests,
        @ApiParam(value = "id",required=true) @PathVariable("id") String id,
        @ApiParam(value = "header-id" ) @RequestHeader(value="header-id", required=false) String headerId) {
}
// 修正後
@Tag(name = "Sample", description = "the Sample API")
public class SampleApiController {
    @Operation(
        operationId = "sampleRequests",
        summary = "サンプル取得処理 ",
        description = "サンプル取得処理 ",
        tags = { "sampleRequests" },
        responses = {
            @ApiResponse(responseCode = "200",
                description = "200 (OK)",
                content = {
                    @Content(mediaType = "application/json",
                    schema = @Schema(implementation = SampleResult.class))
            }),
            @ApiResponse(responseCode = "400",
                description = "400 (Bad Request)",
                content = {
                    @Content(mediaType = "application/json",
                    schema = @Schema(implementation = BadRequest.class))
            }),
            @ApiResponse(responseCode = "500",
                description = "500 (Internal Server Error)",
                content = {
                    @Content(mediaType = "application/json",
                    schema = @Schema(implementation = InternalServerError.class))}
        }
    )
    default ResponseEntity<SampleResult> getSample(
        @Parameter(name = "GetSampleRequests",
            description = "",
            required = true) @Valid @RequestBody GetSampleRequests getSampleRequests,
        @Parameter(name = "id",
            description = "id",
            required = true,
            in = ParameterIn.PATH) @PathVariable("id") String id
        @Parameter(name = "header-id",
            description = "Header Id",
            in = ParameterIn.HEADER) @RequestHeader(value = "header-id", required = false) String headerId
    ) {

}

次に、ライブラリの変更に伴いアプリケーションクラスの@EnableSwagger2のアノテーションを削除しています。

@SpringBootApplication
- @EnableSwagger2
public class SampleApplication {

  public static void main(String[] args) {
    SpringApplication.run(SampleApplication.class, args);
  }
}

openapi-generatorのアップデート

openapi-generatorもこの機会にアップデートをしています。今回のアップデートでは、openapi.config に以下のような設定を追加・削除しています。

-   "java8": true,
+   "useSpringBoot3": true,
+   "generatedConstructorWithRequiredArgs": false,

generatedConstructorWithRequiredArgsを追加したのは、Lombokのアノテーションと競合してしまうのを防ぐためです。というのは、以下のように必須項目であるrequiredの定義をして自動生成したときにコンストラクタが自動生成されてしまうためです。

type: object
properties:
  item_id:
    $ref: ./item_id.yaml
required:
    - item_id

Spock Frameworkのアップデート

テストフレームワークのSpock Frameworkを使用しています。これは以下の通り、groovyのバージョンと共にSpock Frameworkのバージョンアップをすることで対応できました。

-   testImplementation "org.codehaus.groovy:groovy-all:3.0.8"
-   testImplementation "org.spockframework:spock-core:2.0-groovy-3.0"
-   testImplementation "org.spockframework:spock-spring:2.0-groovy-3.0"
-   testImplementation "org.spockframework:spock-guice:2.0-groovy-3.0"
+   testImplementation "org.apache.groovy:groovy-all:4.0.11"
+   testImplementation "org.spockframework:spock-core:2.4-M1-groovy-4.0"
+   testImplementation "org.spockframework:spock-spring:2.4-M1-groovy-4.0"
+   testImplementation "org.spockframework:spock-guice:2.4-M1-groovy-4.0"

まとめ

Spring Bootの2.7から3.0へのバージョンアップに伴う変更内容としては、以下の通りです。

  • ベースとなるSpring Frameworkのバージョン変更によるパッケージの変更
  • Spring MVCのURLマッチングの変更
  • アクセスログのライブラリ変更

Spring Bootのバージョンアップをしたことで、SpringFoxが使えなくなってしまうということもありました。最初にも書きましたが、Spring Boot 2.7と3.0のOSSサポート期間が2023年11月です。そのため、Spring Boot 2.7以下を使用している場合は、早めに3.0以上にバージョンアップしておくのが良いと思いました。

最後に

カート決済部では、仲間を募集しています。ご興味のある方は、こちらからご応募ください。

hrmos.co

カテゴリー