かんがるーさんの日記

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

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その72 )( Executable Jar ファイルを作成しても実行できない原因を調査する+気づいた点を修正する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その71 )( Geb で入力画面1~3→確認画面→完了画面を通したテストを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 実装すべきことが全て完了したので jar ファイルを作成して実行できることを確認しようとしたのですが、なぜか実行できませんでした。原因を調査します。
    • 他に何点か気づいた点を修正します。

参照したサイト・書籍

目次

  1. Executable Jar ファイルを作成しても実行できない原因を調査する
    1. どのような問題が発生しているのか?
    2. build.gradle の mainClass を修正して再度実行する
    3. java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication の原因とは?
    4. build.gradle の dependencies で implementationcompile に戻してみる
    5. Spring Initializr を利用して Spring Boot 2.0.3 のプロジェクトを作って検証する
    6. Spring Initializr を利用して Spring Boot 1.5.14 のプロジェクトを作って検証する
    7. 結論
    8. build.gradle を変更する+動作確認
  2. 気づいた点を修正する
    1. PowerMockito#verifyStatic(VerificationMode) が deprecated になっていたので PowerMockito#verifyStatic(Class, VerificationMode) に変更する

手順

Executable Jar ファイルを作成しても実行できない原因を調査する

どのような問題が発生しているのか?

clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、build/libs の下に boot-npm-geb-sample-1.0.1-RELEASE.jar を作成します。

f:id:ksby:20180722190850p:plain f:id:ksby:20180722190944p:plain

コマンドプロンプトから java -Dspring.profiles.active=develop -jar build\libs\boot-npm-geb-sample-1.0.1-RELEASE.jar を実行すると Exception in thread "main" java.lang.ClassNotFoundException: ksbysample.webapp.lending.Application というエラーメッセージが出力されて実行できませんでした。Application クラスのパッケージが lending になっているということは、build.gradle を修正するのを忘れていますね。。。

f:id:ksby:20180722191212p:plain

build.gradle の mainClass を修正して再度実行する

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

bootRepackage {
    mainClass = 'ksbysample.webapp.bootnpmgeb.Application'
}
  • bootRepackage の mainClass の設定を ksbysample.webapp.lending.Applicationksbysample.webapp.bootnpmgeb.Application に変更します。

jar ファイルを作成し直してから java -Dspring.profiles.active=develop -jar build\libs\boot-npm-geb-sample-1.0.1-RELEASE.jar を実行すると、今度は java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication のエラーメッセージが出力されて実行できません。

f:id:ksby:20180722192336p:plain

java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication の原因とは?

Spring Boot の jar ファイルが Executable Jar ファイル内に出力されていない? ExecutableJar ファイルの中を見てみると、BOOT-INF の下に lib ディレクトリが存在しないことが分かりました。

boot-npm-geb-sample-1.0.1-RELEASE.jar の BOOT-INF の下を見ると、以下のように lib ディレクトリがありませんが、

f:id:ksby:20180722193119p:plain

java -jar コマンドで実行できる ksbysample-webapp-lending の ksbysample-webapp-lending-1.5.4-RELEASE.jar を開いてみると、BOOT-INF/lib ディレクトリが存在し、その下に jar ファイルが格納されています。

f:id:ksby:20180722192819p:plain

build.gradle の dependencies で implementationcompile に戻してみる

原因がよく分からなかったのですが、build.gradle を見ていて、そう言えば Spring Boot + npm + Geb で入力フォームを作ってテストする ( その65 )( Gradle を 4.6 → 4.8.1 へ、Checkstyle を 8.8 → 8.11 へ、PMD を 6.4.0 → 6.5.0 へ、error-prone を 2.2.0 → 2.3.1 へバージョンアップする ) で依存関係の記述の仕方を compileimplementation に変更したことを思い出しました。これが原因のような気がします。一旦 compile に戻してみます。

build.gradle で1つだけ implementationcompile に戻してみます。

dependencies {
    ..........

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix F. Dependency versions ( https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/html/appendix-dependency-versions.html ) 参照
    compile("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf") {
        exclude group: "org.codehaus.groovy", module: "groovy"
    }

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

jar ファイルを作成し直して開いてみると、今度は BOOT-INF/lib ディレクトリが存在し、その下に jar ファイルが格納されていました。

f:id:ksby:20180722194853p:plain

ということは、Spring Boot Gradle Plugin が implementation に対応していないということでしょうか。。。

Spring Initializr を利用して Spring Boot 2.0.3 のプロジェクトを作って検証する

Spring Initializr を利用してプロジェクトを作成して、build.gradle に implementation で記述していると Executable Jar ファイルを作成した時に BOOT-INF/libs を作成してくれるのか否かを検証してみます。まずは 2系の最新バージョンである 2.0.3 から。

IntelliJ IDEA で Spring Initializr を利用してプロジェクトを作成します。

f:id:ksby:20180722203303p:plain f:id:ksby:20180722203353p:plain f:id:ksby:20180722203446p:plain f:id:ksby:20180722203541p:plain f:id:ksby:20180722203659p:plain

プロジェクトが作成されたら build.gradle を開き、compileimplementationruntimeruntimeOnlytestCompiletestImplementation に変更します。あと devtools が runtime で記述されていることに気づきました。あとで反映しましょう。

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

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    runtimeOnly('org.springframework.boot:spring-boot-devtools')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新した後、build タスクを実行して jar ファイルを作成します。2系から demo-0.0.1-SNAPSHOT.jar.original って出来なくなったんですね。

f:id:ksby:20180722204209p:plain

作成された jar ファイルを開くと BOOT-INF/lib ディレクトリが存在し、その下に jar ファイルが出力されていますね。。。?

f:id:ksby:20180722204501p:plain

Spring Initializr を利用して Spring Boot 1.5.14 のプロジェクトを作って検証する

1.5.14 のプロジェクトも作成してみます。

IntelliJ IDEA で Spring Initializr を利用してプロジェクトを作成します。

f:id:ksby:20180722204831p:plain f:id:ksby:20180722204925p:plain f:id:ksby:20180722205019p:plain

プロジェクトが作成されたら build.gradle を開き、compileimplementationruntimeruntimeOnlytestCompiletestImplementation に変更します。

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

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    runtimeOnly('org.springframework.boot:spring-boot-devtools')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}

Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新した後、build タスクを実行して jar ファイルを作成します。

f:id:ksby:20180722210459p:plain

作成された jar ファイルを開くと BOOT-INF/lib ディレクトリが存在しませんでした。

f:id:ksby:20180722211758p:plain

結論

  • 2系の Spring Boot Gradle Plugin は build.gradle の implementation に対応しているが、1系の Spring Boot Gradle Plugin は対応していない。
  • 1系で build.gradle の dependencies に compile ではなく implementation で記述すると、Executable Jar ファイルを作成した時に BOOT-INF/lib ディレクトリが作成されず外部モジュールの jar ファイルが格納されないため、java -jar コマンドで実行しようとしても実行できない。

build.gradle を変更する+動作確認

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

dependencies {
    def spockVersion = "1.1-groovy-2.4"
    def domaVersion = "2.19.2"
    def lombokVersion = "1.18.0"
    def errorproneVersion = "2.3.1"
    def powermockVersion = "1.7.4"
    def seleniumVersion = "3.13.0"

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

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    compile("com.integralblue:log4jdbc-spring-boot-starter:1.0.2")
    compile("org.flywaydb:flyway-core:5.1.4")
    compile("com.h2database:h2:1.4.192")
    compile("com.github.rozidan:modelmapper-spring-boot-starter:1.0.0")
    compile("com.google.guava:guava:25.1-jre")
    compile("org.apache.commons:commons-lang3:3.7")
    testImplementation("org.dbunit:dbunit:2.5.4")
    testImplementation("org.assertj:assertj-core:3.10.0")
    testImplementation("org.spockframework:spock-core:${spockVersion}")
    testImplementation("org.spockframework:spock-spring:${spockVersion}")
    testImplementation("com.google.code.findbugs:jsr305:3.0.2")
    testImplementation("org.jsoup:jsoup:1.11.3")
    testImplementation("com.icegreen:greenmail:1.5.7")

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

    // for Doma
    annotationProcessor("org.seasar.doma:doma:${domaVersion}")
    compile("org.seasar.doma:doma:${domaVersion}")
    domaGenRuntime("org.seasar.doma:doma-gen:${domaVersion}")
    domaGenRuntime("com.h2database:h2:1.4.192")

    // 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-mockito:${powermockVersion}")

    // for Geb + Spock
    testImplementation("org.gebish:geb-spock:2.1")
    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}")
}
  • implementationcompile に戻します。
  • compile("org.springframework.boot:spring-boot-devtools")runtimeOnly("org.springframework.boot:spring-boot-devtools") に変更します。

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

clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、build/libs の下に boot-npm-geb-sample-1.0.1-RELEASE.jar を作成します。boot-npm-geb-sample-1.0.1-RELEASE.jar を開くと BOOT-INF/lib ディレクトリが存在し jar ファイルが格納されていました。

f:id:ksby:20180722224047p:plain

コマンドプロンプトから java -Dspring.profiles.active=develop -jar build\libs\boot-npm-geb-sample-1.0.1-RELEASE.jar を実行すると今度は正常に起動できました。

f:id:ksby:20180722224542p:plain f:id:ksby:20180722224831p:plain

気づいた点を修正する

PowerMockito#verifyStatic(VerificationMode) が deprecated になっていたので PowerMockito#verifyStatic(Class, VerificationMode) に変更する

見つけたのは偶然ですが、InquiryInput02FormValidatorTest クラスの "メールアドレスの Validation のテスト"() テストメソッドで PowerMockito#verifyStatic(VerificationMode) を呼び出しているところに取り消し線が引かれていました。deprecated になっており、PowerMockito#verifyStatic(Class, VerificationMode) に変更するよう PowerMockito#verifyStatic(VerificationMode)JavaDoc に記述されていましたので、変更します。

src/test/groovy/ksbysample/webapp/bootnpmgeb/web/inquiry/form/InquiryInput02FormValidatorTest.groovy の以下の点を変更します。

        @Test
        void "メールアドレスの Validation のテスト"() {
            ..........

            then: "入力チェックエラーは発生しない"
            assert errors.hasErrors() == false
            assert errors.getAllErrors().size() == 0
            // EmailValidator.validate が呼び出されていることをチェックする
            PowerMockito.verifyStatic(EmailValidator, Mockito.times(1))
            EmailValidator.validate("taro.tanaka@sample.co.jp")

            ..........
        }
  • PowerMockito.verifyStatic(Mockito.times(1))PowerMockito.verifyStatic(EmailValidator, Mockito.times(1)) に変更します。

履歴

2018/07/23
初版発行。