かんがるーさんの日記

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

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その98 )( Gradle を 6.4 → 6.9.1 へ、Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップする )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その97 )( webpack を 4.43.0 → 5.56.0 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Gradle を 6.4 → 6.9.1 へバージョンアップします。
    • Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップします。

参照したサイト・書籍

  1. Spring Boot 2.4.2 and Thymeleaf 3.0.12 - access static methods
    https://stackoverflow.com/questions/66048129/spring-boot-2-4-2-and-thymeleaf-3-0-12-access-static-methods/66053568

  2. Improve restricted expression evaluation mode
    https://github.com/thymeleaf/thymeleaf/issues/809

目次

  1. バージョンアップの進め方を決める
  2. Gradle を 6.4 → 6.9.1 へバージョンアップする
  3. Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップする

手順

バージョンアップの進め方を決める

以下の手順で進めます。

  1. JDK 11 のまま、Gradle を 6系最新へ、Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップする(Groovy は 2系のまま)。
  2. JDK 11 のまま、Gradle を 7系最新へ、Spring Boot を 2.4.10 → 2.5.4 へ、Geb を 4.1 → 5.0 へバージョンアップする(Groovy を 3系へ)。
  3. JDK 17 へ、Spring Boot を 2.5.4 → 2.5.5 へバージョンアップする。

Gradle を 6.4 → 6.9.1 へバージョンアップする

build.gradle の wrapper タスクの記述を以下のように変更します。

wrapper {
    gradleVersion = "6.9.1"
    distributionType = Wrapper.DistributionType.ALL
}
  • gradleVersion = "6.4"gradleVersion = "6.9.1" に変更します。

コマンドプロンプトから gradlew wrapper --gradle-version=6.9.1 コマンドを実行します。。。が、java.lang.IllegalArgumentException: Unsupported class file major version 61 のエラーが出力されてバージョンアップできませんでした。

f:id:ksby:20211009004629p:plain

いろいろ試してみたところ、どうも 6.4 の Gradle Deamon が起動している時に gradlew wrapper --gradle-version=6.9.1 コマンドを実行すると、このエラーが出るような気がします。PC を再起動して Gradle Daemon が何も実行されていない状態にしてみます(後で知りましたが gradlew --stop コマンドを実行すれば Gradle Daemon は停止できました)。

PC 再起動後、コマンドプロンプトから gradlew wrapper --gradle-version=6.9.1gradlew --version コマンドを実行すると今度は正常に終了しました。

f:id:ksby:20211009011624p:plain

gradle/wrapper/gradle-wrapper.properties は以下の内容になります。

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

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

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると "BUILD SUCCESSFUL" のメッセージが出力されました。

f:id:ksby:20211009012929p:plain

Spring Boot を 2.2.7 → 2.4.10 へ、Geb を 3.4 → 4.1 へバージョンアップする

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

buildscript {
    ext {
        group "ksbysample"
        version "2.4.10"
    }
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
    dependencies {
        // for doma-codegen-plugin
        classpath "com.h2database:h2:1.4.200"
    }
}

plugins {
    id "java"
    id "eclipse"
    id "idea"
    id "org.springframework.boot" version "2.4.10"
    id "io.spring.dependency-management" version "1.0.11.RELEASE"
    id "groovy"
    id "net.ltgt.errorprone" version "1.1.1"
    id "checkstyle"
    id "com.github.spotbugs" version "4.0.8"
    id "pmd"
    id "com.github.node-gradle.node" version "3.1.1"
    id "com.gorylenko.gradle-git-properties" version "2.3.1"
    id "com.energizedwork.webdriver-binaries" version "1.4"
    id "org.seasar.doma.codegen" version "1.4.1"
}

..........

dependencyManagement {
    imports {
        // mavenBom は以下の URL のものを使用する
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-starter-parent/2.1.4.RELEASE/
        // bomProperty に指定可能な property は以下の URL の BOM に記述がある
        // https://repo.spring.io/release/org/springframework/boot/spring-boot-dependencies/2.1.4.RELEASE/spring-boot-dependencies-2.1.4.RELEASE.pom
        mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
        mavenBom("org.junit:junit-bom:5.7.2")
    }
}

dependencies {
    def spockVersion = "1.3-groovy-2.5"
    def domaVersion = "2.49.0"
    def lombokVersion = "1.18.22"
    def errorproneVersion = "2.3.4"
    def powermockVersion = "2.0.7"
    def seleniumVersion = "3.141.59"
    def spotbugsVersion = "4.0.2"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix F. Dependency versions ( https://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/html/appendix-dependency-versions.html ) 参照
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-freemarker")
    implementation("org.springframework.boot:spring-boot-starter-mail")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    runtimeOnly("org.springframework.boot:spring-boot-devtools")
    implementation("org.springframework.session:spring-session-core")
    implementation("org.springframework.session:spring-session-jdbc")
    implementation("org.codehaus.janino:janino")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude group: "org.junit.vintage", module: "junit-vintage-engine"
    }
    testImplementation("org.springframework.security:spring-security-test")
    testImplementation("org.yaml:snakeyaml")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    implementation("com.integralblue:log4jdbc-spring-boot-starter:2.0.0")
    implementation("org.flywaydb:flyway-core:5.2.4")
    implementation("com.h2database:h2:1.4.200")
    implementation("com.github.rozidan:modelmapper-spring-boot-starter:2.3.1")
    implementation("com.google.guava:guava:27.1-jre")
    implementation("org.apache.commons:commons-lang3:3.8.1")
    testImplementation("org.dbunit:dbunit:2.7.0")
    testImplementation("org.assertj:assertj-core:3.16.0")
    testImplementation("org.spockframework:spock-core:${spockVersion}")
    testImplementation("org.spockframework:spock-spring:${spockVersion}")
    testImplementation("org.jsoup:jsoup:1.13.1")
    testImplementation("com.icegreen:greenmail:1.5.13")

    // for lombok
    annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
    compileOnly("org.projectlombok:lombok:${lombokVersion}")

    // for Doma
    implementation("org.seasar.doma:doma-core:${domaVersion}")
    annotationProcessor("org.seasar.doma:doma-processor:${domaVersion}")

    // for Error Prone ( http://errorprone.info/ )
    errorprone("com.google.errorprone:error_prone_core:${errorproneVersion}")
    compileOnly("com.google.errorprone:error_prone_annotations:${errorproneVersion}")

    // PowerMock
    testImplementation("org.powermock:powermock-module-junit4:${powermockVersion}")
    testImplementation("org.powermock:powermock-api-mockito2:${powermockVersion}")

    // for Geb + Spock
    testImplementation("org.gebish:geb-spock:3.4") {
        exclude group: "org.codehaus.groovy", module: "groovy-all"
    }
    testImplementation("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-support:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-api:${seleniumVersion}")
    testImplementation("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")

    // for SpotBugs
    compileOnly("com.github.spotbugs:spotbugs:${spotbugsVersion}")
    compileOnly("net.jcip:jcip-annotations:1.0")
    compileOnly("com.github.spotbugs:spotbugs-annotations:${spotbugsVersion}")
    testImplementation("com.google.code.findbugs:jsr305:3.0.2")
    spotbugsStylesheets("com.github.spotbugs:spotbugs:${spotbugsVersion}")
    spotbugsPlugins("com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.1")

    // for JUnit 5
    // junit-jupiter で junit-jupiter-api, junit-jupiter-params, junit-jupiter-engine の3つが依存関係に追加される
    testCompile("org.junit.jupiter:junit-jupiter")
    testRuntime("org.junit.platform:junit-platform-launcher")
}

..........

Spring Boot 2.4.10 へのバージョンアップとして以下の点を変更します。

  • buildscript block の以下の点を変更します。
    • version "2.2.7-RELEASE"version "2.4.10"
  • plugins block の以下の点を変更します。
    • id "org.springframework.boot" version "2.2.7.RELEASE"id "org.springframework.boot" version "2.4.10"
    • id "io.spring.dependency-management" version "1.0.9.RELEASE"id "io.spring.dependency-management" version "1.0.11.RELEASE"
  • dependencies block の以下の点を変更します。
    • implementation("org.springframework.boot:spring-boot-starter-validation") を追加します。

各種ライブラリのバージョンアップとして以下の点を変更します。

  • plugins block の以下の点を変更します。
    • id "com.gorylenko.gradle-git-properties" version "2.2.2"id "com.gorylenko.gradle-git-properties" version "2.3.1"
    • id "org.seasar.doma.codegen" version "0.0.2"id "org.seasar.doma.codegen" version "1.4.1"
  • dependencyManagement block の以下の点を変更します。
    • mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATESmavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) { bomProperty "groovy.version", "2.5.15" }
    • mavenBom("org.junit:junit-bom:5.6.2")mavenBom("org.junit:junit-bom:5.7.2")
  • dependencies block の以下の点を変更します。
    • def lombokVersion = "1.18.12"def lombokVersion = "1.18.20"
    • def domaVersion = "2.34.0"def domaVersion = "2.49.0"
    • implementation("org.flywaydb:flyway-core:5.2.4")implementation("org.flywaydb:flyway-core:7.14.1")
    • implementation("com.google.guava:guava:27.1-jre")implementation("com.google.guava:guava:31.0.1-jre")
    • implementation("org.apache.commons:commons-lang3:3.8.1")implementation("org.apache.commons:commons-lang3:3.12.0")
    • testImplementation("org.dbunit:dbunit:2.7.0")testImplementation("org.dbunit:dbunit:2.7.2")
    • testImplementation("org.assertj:assertj-core:3.16.0")testImplementation("org.assertj:assertj-core:3.21.0")
    • testImplementation("org.jsoup:jsoup:1.13.1")testImplementation("org.jsoup:jsoup:1.14.3")
    • testImplementation("com.icegreen:greenmail:1.5.13")testImplementation("com.icegreen:greenmail:1.6.5")
    • testImplementation("org.gebish:geb-spock:3.4") {testImplementation("org.gebish:geb-spock:4.1") {

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

clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると compileJava タスクで警告が1件、testJUnit4AndSpock タスクでテストが大量に失敗しました。

f:id:ksby:20211009141211p:plain

compileJava タスクの警告は error-prone が build/generated の下のソースをチェックして出力していました。自動生成されたコードをチェックしないよう build.gradle に disableWarningsInGeneratedCode = true の設定を追加します。

[compileJava, compileTestGroovy, compileTestJava]*.options*.encoding = "UTF-8"
[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ["-Xlint:all,-options,-processing,-path"]
tasks.withType(JavaCompile).configureEach {
    options.errorprone {
        disableWarningsInGeneratedCode = true
    }
}
tasks.named("compileTestJava").configure {
    options.errorprone.enabled = false
}

testJUnit4AndSpock タスクでテストが大量に失敗している方は、失敗したテストを 1件だけ IntelliJ IDEA 上から実行してみると Caused by: org.flywaydb.core.api.FlywayException: Found non-empty schema(s) "PUBLIC" but no schema history table. Use baseline() or set baselineOnMigrate to true to initialize the schema history table. というエラーメッセージが出力されていました。

f:id:ksby:20211009165748p:plain

Flyway が実行される時に PUBLIC スキーマが空でなく、spring.flyway.baseline-on-migrate=true を設定すれば schema history table を初期化してくれるとのことですが、spring.flyway.baseline-on-migrate=true を設定するのではなく PUBLIC テーブルが空でない原因を調べることにします。

application.properties を見ると以下の設定が記述されており、明らかにこれですね。。。 おそらくバージョンアップ前は Flywasy が先に実行されて Sprig Session のテーブル作成はその後に実行されていたのが、バージョンアップ後はその順序が逆になったものと思われます。

spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=embedded
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-h2.sql
spring.session.jdbc.table-name=SPRING_SESSION

spring.session.jdbc.initialize-schema=embeddedspring.session.jdbc.initialize-schema=never に変更して Spring Session のテーブルを自動作成しないように変更してから、

spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=never
spring.session.jdbc.table-name=SPRING_SESSION

失敗していたテスト(このテストは Spring Session の機能を利用していない)を実行してみると、成功するようになりました。

f:id:ksby:20211009171400p:plain

spring.session.jdbc.initialize-schema=never の設定はそのままにして、Flyway で Spring Session のテーブルを作成するようにします。

src/main/resources/db/migration の下に V1.2__create_spring_session_schema.sql を新規作成した後、org/springframework/session/jdbc/schema-h2.sql の内容をコピーします。

-- copy from org/springframework/session/jdbc/schema-h2.sql
CREATE TABLE SPRING_SESSION
(
    PRIMARY_ID            CHAR(36) NOT NULL,
    SESSION_ID            CHAR(36) NOT NULL,
    CREATION_TIME         BIGINT   NOT NULL,
    LAST_ACCESS_TIME      BIGINT   NOT NULL,
    MAX_INACTIVE_INTERVAL INT      NOT NULL,
    EXPIRY_TIME           BIGINT   NOT NULL,
    PRINCIPAL_NAME        VARCHAR(100),
    CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES
(
    SESSION_PRIMARY_ID CHAR(36)      NOT NULL,
    ATTRIBUTE_NAME     VARCHAR(200)  NOT NULL,
    ATTRIBUTE_BYTES    LONGVARBINARY NOT NULL,
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION (PRIMARY_ID) ON DELETE CASCADE
);

再度 clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、テストがまだ 2件失敗します。

f:id:ksby:20211009200156p:plain

原因を調査すると、改行コードを <br/> に変換している th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), T(java.lang.System).getProperty('line.separator'), '&lt;br /&gt;')} : ''" のところで org.attoparser.ParseException: Instantiation of new objects and access to static classes is forbidden in this context というエラーメッセージが出力されていました。

              <tr>
                <th nowrap>お問い合わせの内容</th>
                <td id="inquiry"
                    th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), T(java.lang.System).getProperty('line.separator'), '&lt;br /&gt;')} : ''">
                  ここに、<br/>
                  入力されたお問い合わせの内容が表示されます。
                </td>
              </tr>

Web で調べると Spring Boot 2.4.2 and Thymeleaf 3.0.12 - access static methods を見つけました。Thymeleaf 3.0.12 からセキュリティ強化のために T(identifier) in SpringEL の形式では呼び出せなくなったとのこと。

System.getProperty を呼び出す Bean を作成して、Thymeleaf テンプレートから呼び出すように変更します。

まず src/main/java/ksbysample/webapp/bootnpmgeb/helper の下に thymeleaf パッケージを作成し、その下に SystemPropertiesHelper.java を新規作成して以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.helper.thymeleaf;

import org.springframework.boot.system.SystemProperties;
import org.springframework.stereotype.Component;

/**
 * System Property 値取得用 Helper クラス
 */
@Component("sph")
public class SystemPropertiesHelper {

    /**
     * System#getProperty を呼び出して指定された system property の値を取得する
     *
     * @param properties 値を取得する system property 名
     * @return 取得した system property の値
     */
    public String getProperty(String... properties) {
        return SystemProperties.get(properties);
    }

}

src/main/resources/templates/web/inquiry/confirm.html を以下のように変更します。

                            <tr>
                                <th nowrap>お問い合わせの内容</th>
                                <td id="inquiry"
                                    th:utext="*{inquiry} ? ${#strings.replace(#strings.escapeXml(confirmForm.inquiry), @sph.getProperty('line.separator'), '&lt;br /&gt;')} : ''">
                                    ここに、<br/>
                                    入力されたお問い合わせの内容が表示されます。
                                </td>
                            </tr>
  • T(java.lang.System).getProperty('line.separator')@sph.getProperty('line.separator') に変更します。

再度 clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、今度は "BUILD SUCCESSFUL" のメッセージが出力されました。

f:id:ksby:20211009203616p:plain

gebTest タスクも "BUILD SUCCESSFUL" のメッセージが表示されました。Geb のバージョンアップは特に問題は発生しないようです。

f:id:ksby:20211009205545p:plain

履歴

2021/10/10
初版発行。