SpringBoot1.5 + Gradle4.4 + Java8 + Docker 環境を Java11 に対応させる

この記事は、[Java Advent Calendar 2019](https://qiita.com/advent-calendar/2019/java) 15日目の記事です。

## はじめに
Java8の無償サポートが2019年1月に切れてから一年ほど放置してましたが、
今回時間ができたので8以降初のLTSであるJava11への更新を行いました
その時のメモです

と書き始めようとしましたが、OpenJDK8は2023年6月までサポートされるらしいので、
OracleJDKからOpenJDKに変えるだけでよかったのでは......
でもやってしまったので記録しときます

## JDKのアップデート

### AdoptOpenJDK11をインストール

以下のコマンドを入れると最新のJDKがインストールされる

```
brew cask install adoptopenjdk
```

しかし、現状ではJDK13が入ってしまうので、LTS版であるJDK11を入れるにはバージョンを指定する必要がある
ということで公開されているバージョンを検索

```
$ brew search adoptopenjdk
==> Casks
adoptopenjdk                       adoptopenjdk11-openj9-jre          adoptopenjdk12-openj9              adoptopenjdk13-jre                 adoptopenjdk8                      adoptopenjdk8-openj9-large
adoptopenjdk10                     adoptopenjdk11-openj9-jre-large    adoptopenjdk12-openj9-jre          adoptopenjdk13-openj9              adoptopenjdk8-jre                  adoptopenjdk9
adoptopenjdk11                     adoptopenjdk11-openj9-large        adoptopenjdk12-openj9-jre-large    adoptopenjdk13-openj9-jre          adoptopenjdk8-openj9
adoptopenjdk11-jre                 adoptopenjdk12                     adoptopenjdk12-openj9-large        adoptopenjdk13-openj9-jre-large    adoptopenjdk8-openj9-jre
adoptopenjdk11-openj9              adoptopenjdk12-jre                 adoptopenjdk13                     adoptopenjdk13-openj9-large        adoptopenjdk8-openj9-jre-large
```

今回入れたいのはJDK11なので、以下のコマンドでバージョンを指定してインストール

```
brew cask install adoptopenjdk11
```

すでに通っているPATHをJDK11に変更

```
export JAVA_HOME=`/usr/libexec/java_home -v 11`
```

バージョンを確認

```
$ java -version
openjdk version "11.0.5" 2019-10-15
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.5+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.5+10, mixed mode)
```

### IntelliJのProjectSDKにJDK11を指定

Command + ; でProjectStructureを開き、ProjectSDKとProjectLunguageLebelにJDK11を指定

## Gradleをアップデート

Gradle4.4はJDK11に対応していないため、アップデートする必要がある
5.0以降のバージョンは対応しているようだが、折角なので現在の最新である6.0.1にしてみる

### gradle-wrapper.propertiesの更新

gradle/wrapper/gradle-wrapper.properties に記載されているバージョンを更新すればプロジェクトに適用されるGradleのバージョンが変更される
今回は以下の 4.4 の部分を 6.0.1 に変更するだけ

```
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip
```

### gradlewを更新

それだけだとgradlewに変更が伝播しないので、gradlewを生成し直す

```
./gradlew wrapper
```

## gradle.buildを更新

各種フレームワーク・ライブラリをJDK11に対応したものに変更していく
基本的には全てgradle.buildに記載されているので、それを修正していけばよい

### Javaのバージョン指定を変更

各指定の意味は[公式](https://docs.gradle.org/current/userguide/java_plugin.html)に記載されているので、必要な物を指定する

```
sourceCompatibility = 11
targetCompatibility = 11
```

### Gradle6系の記載方法に変更

jarの指定方法が変更になっているので更新
詳細は[公式](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html)を参照のこと

```
jar {
    baseName = 'example'
    version = '1.0.0-SNAPSHOT'
}
```

上記だったものを以下に変更

```
bootJar {
    archiveBaseName = "example"
    archiveVersion = "1.0.0-SNAPSHOT"
}
```

### SpiringBootのバージョンを変更

Java11を利用するにはSpringBoot2.1.x以上が必要
折角なので現在の最新である2.2.2にしてみる
基本的には[公式のマイグレーションガイド](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide)に従えばいい

```
buildscript {
    ext {
        springBootVersion = '1.5.13.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
```

SpringBootが自動で依存関係管理プラグインを適用しなくなったので、プラグインも追加する

```
apply plugin: 'io.spring.dependency-management'
```

メインクラスの指定方法が変わっているので、それも変更

```
springBoot {
    mainClassName = "jp.co.example.Application"
}
```

### ライブラリのバージョンを更新

[MavenRepository](https://mvnrepository.com/)を見ながらライブラリのバージョンを更新していく

#### SpringBoot周りのライブラリ

SpringBoot周りのライブラリはバージョンを変数で指定しておくと楽
ただし、一部SpringBootのバージョンと異なる場合があるので注意が必要

```
dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
    compile("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}")
    compile("org.springframework.boot:spring-boot-starter-security:${springBootVersion}")
    compile("org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}")

    testCompile("org.springframework.boot:spring-boot-starter-test:${springBootVersion}")
    testCompile("org.springframework.security:spring-security-test:5.2.1.RELEASE")
}
```

#### lombok

依存関係の指定方法が変わっているため、普通にバージョンだけ指定して更新するとビルド時にlombokで自動生成されるメソッドが見つからないとエラーが出る
[公式](https://projectlombok.org/setup/gradle)を参考にして、以下の指定を追加する

```
compileOnly('org.projectlombok:lombok:1.18.10')
annotationProcessor('org.projectlombok:lombok:1.18.10')
```

また、@Valueを使っていると、Jacksonでのデシリアライズがうまく機能しないエラーが発生する
project直下に lombok.config というファイルを作って以下を記載することで回避できる
その他の設定については[公式のマイグレーションガイド](https://projectlombok.org/changelog)に記載がある

```
lombok.addJavaxGeneratedAnnotation = false
lombok.addLombokGeneratedAnnotation = true
lombok.noArgsConstructor.extraPrivate = false
lombok.anyConstructor.addConstructorProperties = true

config.stopBubbling = true
```

#### jjwt

一部のアルゴリズムを利用する際にJava11で廃止されたAPIに依存しているため、
Java8時点で利用していてJava11に移行してきた場合、依存関係を追加する必要がある
[公式](https://github.com/jwtk/jjwt)を参考に以下を追加する

```
compile('javax.xml.bind:jaxb-api:2.3.0')
compile('com.sun.xml.bind:jaxb-core:2.3.0')
compile('com.sun.xml.bind:jaxb-impl:2.3.0')
```

#### その他

よしなに更新する
dependencies 以外で指定しているライブラリについても忘れず更新すること
jacoco の更新を忘れて時間を無駄にしました

## Java11で廃止になったAPIに対応する

[この記事](https://qiita.com/mao172/items/dbf41e7a246c3f87dcbe)を参考に廃止になったAPIを修正していく


## SpringBoot2.0で廃止になったクラスを修正する

ビルドしてみるとSpringBoot周りで廃止されたクラスが複数出てくるので、それを修正していく
[公式のマイグレーションガイド](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide)を参考にする

今回はエラーメッセージを制御していた org.springframework.boot.autoconfigure.web 周りのクラスだけが対象だった
以下のメソッドを

```
@RequestMapping(value = "/error")
public ErrorResponse error(HttpServletRequest request, HttpServletResponse response) {
  ServletRequestAttributes attributes = new ServletRequestAttributes(request);
  Throwable error = errorAttributes.getError(attributes);
  return convertException(error);
}
```

こうする

```
@RequestMapping(value = "/error")
public ErrorResponse error(WebRequest request) {
  Throwable error = errorAttributes.getError(request);
  return convertException(error);
}
```

### テストしてみる

ここまでやるととりあえずビルドが通るようになるので、テストを回してデグレが起きていないか確認する
今回は特に問題なさそう
Warnの解消は後回しにして、とりあえずJDK11対応済みのアプリをDockerに固めてデプロイすることを目指す

## Dockerfileを変更する

Dockerfileはベースイメージの変更だけでよい
どのイメージを選択すればいいかは[この記事](https://k11i.biz/blog/2018/10/29/base-docker-images-for-java11/)が参考になる
今回はおすすめの通り、adoptopenjdk/openjdk11:alpine-slim を選択する

```
FROM adoptopenjdk/openjdk11:alpine-slim
```

## その他

### CircleCIの設定を変更する

CI/CDツールを利用している場合はそちらの設定も忘れず変更すること
テスト環境構築時のベースイメージの指定等、忘れがち

### SpringBootActuator

SpringBootActuatorで自動生成される全てのエンドポイントに/actuatorのprefixが追加されているので注意が必要
特に /health を利用しているとデプロイがうまくいかなくなる
その他の変更点は[この記事](https://qiita.com/suke_masa/items/acfdf52019538fd8ccc6)が参考になる


## さいごに
以上の修正でとりあえずデプロイできる状態にはなっているはずです
何か問題が起きれば適宜追記していきます
「こっちの書き方の方が正しい」「これだとバグる可能性がある」等あれば是非教えてください

## 参考
[mac上にhomebrewでAdoptOpenJDK11をインストール](https://qiita.com/tarhashi/items/d166741e1a5ee78db242)

[Gradle wrapper のバージョン更新についてのメモ](https://qiita.com/nobuoka/items/09cbdcd4716b930abdc4)

[Spring Boot 1.5.10 → Spring Boot 2.0.0 にしたときの覚書](https://qiita.com/yukina-ge/items/1ca029ed69494bfd36d6)

[Spring Boot 2.0 Migration Guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide)

[Lombok Project](https://projectlombok.org/setup/gradle)

[Java 11 リリース後のオススメ Docker イメージを考える](https://k11i.biz/blog/2018/10/29/base-docker-images-for-java11/)

[Spring Boot 2.0のActuator、とりあえず動かすために知っておきたい変更点3つ](https://qiita.com/suke_masa/items/acfdf52019538fd8ccc6)

[Javaエンジニアが Java 11 リリースに向けて備えておくべきこと](https://qiita.com/mao172/items/dbf41e7a246c3f87dcbe)

[ObjectMapper can't deserialize without default constructor after upgrade to Spring Boot 2](https://stackoverflow.com/questions/48330613/objectmapper-cant-deserialize-without-default-constructor-after-upgrade-to-spri?rq=1)

[Lombok Changelog](https://projectlombok.org/changelog)