かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot 2.0.x の Web アプリを 2.1.x へバージョンアップする ( その15 )( JDK を 8u202 → 11.0.2+9 に変更する )

概要

記事一覧はこちらです。

Spring Boot 2.0.x の Web アプリを 2.1.x へバージョンアップする ( その14 )( Docker で複数の Tomcat を起動して動作確認する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • プロジェクトで使用する JDK を 8u202 → 11.0.2+9 に変更します。

参照したサイト・書籍

  1. AdoptOpenJDK
    https://adoptopenjdk.net/index.html?variant=openjdk11&jvmVariant=hotspot

  2. Powermock 2.0.0-beta.5 problem with jdk9 modules
    https://github.com/powermock/powermock/issues/864

  3. JDK 9への移行 - ランタイム・アクセス警告の理解
    https://docs.oracle.com/javase/jp/9/migrate/toc.htm#GUID-7BB28E4D-99B3-4078-BDC4-FC24180CE82B

目次

  1. AdoptOpenJDK の jdk-11.0.2+9 をインストールする
  2. ksbysample-webapp-lending プロジェクトが使用する JDK を jdk-11.0.2+9 に変更する
  3. build.gradle を変更する
  4. clean タスク → Rebuild Project → build タスクを実行してみる
  5. build 時のエラーの原因を調査する
    1. SampleHelperTest$異常処理のテスト.SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする テストの java.lang.IllegalAccessError
    2. WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.6/6936e700f0fb1b50bac0698ada4347a769d40199/groovy-2.5.6.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
  6. 続きます。。。

手順

AdoptOpenJDK の jdk-11.0.2+9 をインストールする

https://adoptopenjdk.net/index.html にアクセスした後、「OpenJDK 11 (LTS)」「HotSpot」を選択して「Latest release jdk-11.0.2+9」のボタンをクリックし OpenJDK11U-jdk_x64_windows_hotspot_11.0.2_9.zip をダウンロードします。

f:id:ksby:20190316020934p:plain

OpenJDK11U-jdk_x64_windows_hotspot_11.0.2_9.zip を解凍して作成された jdk-11.0.2+9 ディレクトリを D:\Java\ の下へ移動します。

環境変数 JAVA_HOME のパスを D:\Java\jdk-11.0.2+9 へ変更します。

コマンドプロンプトから java -version を実行し、11.0.2+9 に変更されていることを確認します。

f:id:ksby:20190316022807p:plain

ksbysample-webapp-lending プロジェクトが使用する JDKjdk-11.0.2+9 に変更する

ksbysample-webapp-lending プロジェクトを開いた状態で IntelliJ IDEA のメインメニューから「File」-「Project Structure...」を選択します。

「Project Structure」ダイアログが表示されます。画面左側で「Project Settings」-「Project」を選択後、画面右側の「Project SDK」の「New...」ボタンをクリックし、表示されるメニューから「JDK」を選択します。

f:id:ksby:20190316023428p:plain

「Select Home Directory for JDK」ダイアログが表示されます。D:\Java\jdk-11.0.2+9 を選択した後、「OK」ボタンをクリックします。

f:id:ksby:20190316023623p:plain

「Default Project Structure」ダイアログに戻るので、今度は「Project SDK」の「Edit」ボタンをクリックします。

f:id:ksby:20190316023814p:plain

画面左側で「Platform Settings」-「SDKs」が選択された状態になるので、画面右上の入力フィールドで "11" → "11.0.2+9" へ変更します。

f:id:ksby:20190316024054p:plain

「OK」ボタンをクリックして「Project Structure」ダイアログを閉じます。

メイン画面に戻ると画面右下に「Indexing...」の表示が出るので、終了するまで待ちます。

f:id:ksby:20190316024224p:plain

build.gradle を変更する

build.gradle の以下の点を変更します。

plugins {
    ..........
}

sourceCompatibility = 11
targetCompatibility = 11

wrapper {
    gradleVersion = "5.2.1"
    distributionType = Wrapper.DistributionType.ALL
}

..........
  • sourceCompatibility = 1.8sourceCompatibility = 11 に変更します。
  • targetCompatibility = 1.8targetCompatibility = 11 に変更します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

clean タスク → Rebuild Project → build タスクを実行してみる

clean タスク → Rebuild Project は何のエラーも出ずに終了しましたが、build タスクを実行すると BUILD FAILED になりました。

f:id:ksby:20190316025127p:plain f:id:ksby:20190316025221p:plain

以下の警告が出ており、

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.6/6936e700f0fb1b50bac0698ada4347a769d40199/groovy-2.5.6.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.vmplugin.v7.Java7$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

テストが1つ失敗しています。

ksbysample.webapp.lending.SampleHelperTest > ksbysample.webapp.lending.SampleHelperTest$異常処理のテスト.SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする FAILED
    java.lang.IllegalAccessError

build 時の警告とエラーの原因を調査する

SampleHelperTest$異常処理のテスト.SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする テストの java.lang.IllegalAccessError

失敗したテストは src/test/groovy/ksbysample/webapp/lending/SampleHelperTest.groovy の以下のクラスでした。

    @RunWith(PowerMockRunner)
    @PowerMockRunnerDelegate(SpringRunner)
    @SpringBootTest
    @PrepareForTest(BrowfishUtils)
    @PowerMockIgnore("javax.management.*")
    static class 異常処理のテスト {

        @Autowired
        private SampleHelper sampleHelper

        @Test
        void "SampleHelper_encryptを呼ぶとRuntimeExceptionをthrowする"() {
            // setup:
            PowerMockito.mockStatic(BrowfishUtils)
            PowerMockito.when(BrowfishUtils.encrypt(Mockito.any()))
                    .thenThrow(new NoSuchPaddingException())

            // expect:
            assertThatExceptionOfType(RuntimeException).isThrownBy({
                sampleHelper.encrypt("test")
            })
        }

    }

テストを直接実行してみると、

f:id:ksby:20190316090428p:plain

java.lang.IllegalAccessError: class javax.xml.parsers.FactoryFinder (in unnamed module @0x89c65d5) cannot access class jdk.xml.internal.SecuritySupport (in module java.xml) because module java.xml does not export jdk.xml.internal to unnamed module @0x89c65d5

のエラーメッセージが出力されています。

Web で調べてみると Powermock 2.0.0-beta.5 problem with jdk9 modules の Issue が見つかりました。@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.dom.*"}) を指定すればエラーを回避できるようです。

src/test/groovy/ksbysample/webapp/lending/SampleHelperTest.groovy を以下のように変更します。

    @RunWith(PowerMockRunner)
    @PowerMockRunnerDelegate(SpringRunner)
    @SpringBootTest
    @PrepareForTest(BrowfishUtils)
    @PowerMockIgnore(["javax.management.*", "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.dom.*"])
    static class 異常処理のテスト {
  • @PowerMockIgnore("javax.management.*")@PowerMockIgnore(["javax.management.*", "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.dom.*"]) に変更します。groovy なのでアノテーションの配列の指定は { ... } ではなく [ ... ] になります。

テストを実行すると今度は成功しました。

f:id:ksby:20190316092500p:plain

またこの時点で再度 clean タスク → Rebuild Project → build タスクを実行してみると、警告は出たままですが 最後に BUILD SUCCESSFUL が出力されるようになりました。

f:id:ksby:20190316093614p:plain

WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.6/6936e700f0fb1b50bac0698ada4347a769d40199/groovy-2.5.6.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)

JDK 9への移行 - ランタイム・アクセス警告の理解 によると、警告が出ているのは JDKの内部部分にアクセスするためにコードでリフレクションが使用されているためとのことです。

例えば上の WARNING だと org.codehaus.groovy.vmplugin.v7.Java7$1 が java.lang.invoke.MethodHandles$Lookup にリフレクションでアクセスしていることが原因で、--add-opens java.base/java.lang.invoke=ALL-UNNAMEDJVM のオプションに指定すれば警告が出なくなります。

build タスクを実行し、WARNING が出たら --add-opens java.base/.....=ALL-UNNAMED オプションを追加するを繰り返したところ、groovy で6個、powermock で1個の WARNING が出力されました。

  • WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.6/6936e700f0fb1b50bac0698ada4347a769d40199/groovy-2.5.6.jar) to method java.lang.Object.finalize()
  • WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.6/6936e700f0fb1b50bac0698ada4347a769d40199/groovy-2.5.6.jar) to method java.util.AbstractCollection.hugeCapacity(int)
  • WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.6/6936e700f0fb1b50bac0698ada4347a769d40199/groovy-2.5.6.jar) to method java.lang.reflect.AnnotatedElement.lambda$getDeclaredAnnotationsByType$0(java.lang.annotation.Annotation,java.lang.annotation.Annotation)
  • WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.6/6936e700f0fb1b50bac0698ada4347a769d40199/groovy-2.5.6.jar) to method java.security.SecureClassLoader.getProtectionDomain(java.security.CodeSource)
  • WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedConstructor$1 (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.6/6936e700f0fb1b50bac0698ada4347a769d40199/groovy-2.5.6.jar) to constructor java.io.File(java.lang.String,java.io.File)
  • WARNING: Illegal reflective access by org.powermock.reflect.internal.WhiteboxImpl (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.powermock/powermock-reflect/2.0.0/cd452bc345ec9f88ec5efecd41139de0cb1d4265/powermock-reflect-2.0.0.jar) to method java.lang.ref.Reference.clone()
  • WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass (file:/C:/Users/root/.gradle/caches/modules-2/files-2.1/org.codehaus.groovy/groovy/2.5.6/6936e700f0fb1b50bac0698ada4347a769d40199/groovy-2.5.6.jar) to field java.net.SocketTimeoutException.serialVersionUID

--add-opens java.base/.....=ALL-UNNAMED オプションは build.gradle に以下のように定義します。

def jvmArgsForTask = [
        "-ea",
        "-Dfile.encoding=UTF-8",
        "-Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP"
]
// JDK 11 に変更後、test タスク実行時に groovy と powermock が JDKの内部部分にアクセスするためにコードでリフレクションを使用
// していて WARNING が出るため、JVM の起動時オプションの --add-opens を指定して WARNING が出ないようにする
def jvmArgsAddOpens = [
        "--add-opens=java.base/java.io=ALL-UNNAMED",
        "--add-opens=java.base/java.lang=ALL-UNNAMED",
        "--add-opens=java.base/java.lang.invoke=ALL-UNNAMED",
        "--add-opens=java.base/java.lang.ref=ALL-UNNAMED",
        "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
        "--add-opens=java.base/java.net=ALL-UNNAMED",
        "--add-opens=java.base/java.security=ALL-UNNAMED",
        "--add-opens=java.base/java.util=ALL-UNNAMED"
]
def printTestCount = { desc, result ->
    if (!desc.parent) { // will match the outermost suite
        println "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)"
    }
}

// -ea -Dspring.profiles.active=develop -Dfile.encoding=UTF-8 -Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP
bootRun {
    jvmArgs = jvmArgsForTask + ["-Dspring.profiles.active=develop"]
}

// -ea -Dspring.profiles.active=unittest -Dfile.encoding=UTF-8 -Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP
task testJUnit4AndSpock(type: Test) {
    jvmArgs = jvmArgsForTask +
            jvmArgsAddOpens +
            ["-Dspring.profiles.active=unittest"]

    testLogging {
        afterSuite printTestCount
    }
}
test.dependsOn testJUnit4AndSpock
test {
    jvmArgs = jvmArgsForTask +
            jvmArgsAddOpens +
            ["-Dspring.profiles.active=unittest"]

    // for JUnit 5
    useJUnitPlatform()

    testLogging {
        afterSuite printTestCount
    }
}
  • def jvmArgsAddOpens = [ ... ] を追加します。
  • testJUnit4AndSpock タスクと test タスク内の jvmArgs に値をセットしている箇所に jvmArgsAddOpens + を追加します。

clean タスク → Rebuild Project → build タスクを実行すると WARNING が出なくなりました。

f:id:ksby:20190316131922p:plain

また IntelliJ IDEA の Edit Configuration で JUnitVM Options にも --add-opens java.base/.....=ALL-UNNAMED オプションを指定する必要があります。

VM Options に -ea -Dspring.profiles.active=unittest -Dfile.encoding=UTF-8 -Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP のみで --add-opens java.base/.....=ALL-UNNAMED オプションを1つも設定していないと、

f:id:ksby:20190316133312p:plain

Project Tool Window で src/test/groovy/ksbysample と src/test/java/ksbysample でコンテキストメニューを表示して「Run 'Tests in ''ksbysample'」を選択してテストを実行するとテストは全て成功しますが、WARNING: An illegal reflective access operation has occurred のメッセージが表示されます。

f:id:ksby:20190330122942p:plain f:id:ksby:20190330123501p:plain

JUnitVM Options を -ea -Dspring.profiles.active=unittest -Dfile.encoding=UTF-8 -Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP -ea -Dspring.profiles.active=unittest -Dfile.encoding=UTF-8 -Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.ref=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED に変更するとメッセージが表示されなくなります。

f:id:ksby:20190330123854p:plain

続きます。。。

思ったより簡単に JDK 11 へ切り替えられました。引っかかったのは groovy か powermock 関連でした。

次回は app の Docker イメージを JDK 11 ベースに作り直してから Tomcat を起動して動作確認を行います。またサービスからも起動して動作確認します。

履歴

2019/03/16
初版発行。
2019/03/30
* build.gradle を変更する を追加しました。
* IntelliJ IDEA の Edit Configuration で JUnit の VM Options には--add-opens java.base/.....=ALL-UNNAMEDオプションを指定する必要はありません。 と書いていましたが、指定する必要があったので修正しました。