かんがるーさんの日記

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

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その15 )( Flyway のインストール + Spring Security 使用時に H2 Console に接続する + IntelliJ IDEA の Database Tools で in-memory モードの H2 Database に接続する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その14 )( browser-sync –> Tomcat 連携してファイル変更時に自動リロードで反映される環境を構築してみる ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 本当は Flyway をインストールして H2 Database にテーブルを作成するのをさっと終わらせるだけのつもりでいたのですが、他のことで以外に苦労しました。
    • 以下の内容を記述しています。
      • Flyway のインストールと、Tomcat 起動時の Flyway の動作の確認
      • Spring Security を使用している時に H2 Console に接続する方法
      • IntelliJ IDEA の Database Tools で、Spring Boot の Tomcat から in-memory モードで起動した H2 Database に接続する方法
    • Flyway で H2 Database にテーブルを作成するのは次回に持ち越しです。

参照したサイト・書籍

  1. Flyway
    https://flywaydb.org/

  2. H2 Database Engine
    http://www.h2database.com/html/main.html

  3. Spring Boot Reference Guide - 78. Database initialization - 78.5.1 Execute Flyway database migrations on startup
    https://docs.spring.io/spring-boot/docs/current/reference/html/howto-database-initialization.html#howto-execute-flyway-database-migrations-on-startup

  4. Spring Boot Flyway Sample
    https://github.com/spring-projects/spring-boot/tree/v1.5.6.RELEASE/spring-boot-samples/spring-boot-sample-flyway

  5. SpringBoot:H2DBに接続
    http://web-dev.hatenablog.com/entry/spring-boot/intro/connect-h2db

  6. Spring Boot /h2-console throws 403 with Spring Security 1.5.2
    https://stackoverflow.com/questions/43794721/spring-boot-h2-console-throws-403-with-spring-security-1-5-2

  7. H2 Database Console
    https://springframework.guru/using-the-h2-database-console-in-spring-boot-with-spring-security/

  8. Connect to H2 database using IntelliJ database client
    https://stackoverflow.com/questions/28940912/connect-to-h2-database-using-intellij-database-client

  9. QUERYING THE EMBEDDED H2 DATABASE OF A SPRING BOOT APPLICATION
    https://techdev.de/querying-the-embedded-h2-database-of-a-spring-boot-application/

  10. org.h2.jdbc.JdbcSQLException: Connection is broken: “unexpected status 5505072”
    https://groups.google.com/forum/#!topic/h2-database/qZ5bpboIC6U

目次

  1. Flyway をインストールする
  2. Tomcat を起動してみるが起動せず。。。connection pool の initialSize を 1→2 に変更する
  3. Tocmat 起動時の Flyway の動作を見てみる
  4. IntelliJ IDEA の Database Tools から H2 Database へ接続する。。。が何かおかしい
  5. H2 Console に接続する
  6. in-memory モードで起動している H2 Database に別プロセスから接続するには?
  7. 再び Database Tools から H2 Database へ接続してみる

手順

Flyway をインストールする

Spring IO Platform Reference Guide を見ると org.flywaydb:flyway-core の記述がありますが、バージョンは 3.2.1 でした。Flyway を見ると最新バージョンは 4.2.0 でしたので、バージョン番号を指定して 4.2.0 をインストールします。

build.gradle を以下のように変更します。

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

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    compile("com.integralblue:log4jdbc-spring-boot-starter:1.0.1")
    compile("org.flywaydb:flyway-core:4.2.0")
    testCompile("org.dbunit:dbunit:2.5.3")
    testCompile("com.icegreen:greenmail:1.5.5")
    testCompile("org.assertj:assertj-core:3.8.0")
    testCompile("org.spockframework:spock-core:${spockVersion}")
    testCompile("org.spockframework:spock-spring:${spockVersion}")
    testCompile("com.google.code.findbugs:jsr305:3.0.2")

    ..........
}
  • dependencies に compile("org.flywaydb:flyway-core:4.2.0") を追加します。

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

Tomcat を起動してみるが起動せず。。。connection pool の initialSize を 1→2 に変更する

Flyway をインストールしただけの状態で Tomcat が起動するか確認します。

Tomcat を起動すると Caused by: org.apache.tomcat.jdbc.pool.PoolExhaustedException: [main] Timeout: Pool empty. Unable to fetch a connection in 30 seconds, none available[size:1; busy:1; idle:0; lastwait:30000]. というエラーログが出力されて起動できませんでした。application.properties に spring.datasource.tomcat.initialSize=1 と設定していますが、1 つでは足りないようです。

f:id:ksby:20170813150540p:plain

initialSize の数値を変更して試してみたところ、最低 2 で設定していないと起動できませんでした。application.properties、application-product.properties の設定を以下のように変更します。

spring.datasource.tomcat.initialSize=2
spring.datasource.tomcat.maxActive=2
spring.datasource.tomcat.maxIdle=2
spring.datasource.tomcat.minIdle=2

再度試すと Tomcat が無事起動しました。

f:id:ksby:20170813151035p:plain

Tocmat 起動時の Flyway の動作を見てみる

Log4jdbc Spring Boot Starter により Tomcat 起動時に Flyway で実行される SQL 文がコンソールに出力されていたので、追ってみました。

  1. Connection.new
  2. CALL SCHEMA()
  3. SELECT COUNT(*) FROM INFORMATION_SCHEMA.schemata WHERE schema_name=‘PUBLIC’
  4. SET SCHEMA “PUBLIC”
  5. SELECT COUNT(*) FROM INFORMATION_SCHEMA.schemata WHERE schema_name=‘PUBLIC’
  6. SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLEs WHERE TABLE_schema = ‘PUBLIC’ AND TABLE_TYPE = ‘TABLE’
  7. CALL SCHEMA()
  8. CREATE TABLE “PUBLIC”.“schema_version” ( “installed_rank” INT NOT NULL, “version” VARCHAR(50), “description” VARCHAR(200) NOT NULL, “type” VARCHAR(20) NOT NULL, “script” VARCHAR(1000) NOT NULL, “checksum” INT, “installed_by” VARCHAR(100) NOT NULL, “installed_on” TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, “execution_time” INT NOT NULL, “success” BOOLEAN NOT NULL )
  9. ALTER TABLE “PUBLIC”.“schema_version” ADD CONSTRAINT “schema_version_pk” PRIMARY KEY (“installed_rank”)
  10. CREATE INDEX “PUBLIC”.“schema_version_s_idx” ON “PUBLIC”.“schema_version” (“success”)
  11. select * from “PUBLIC”.“schema_version” for update
  12. SELECT “installed_rank”,“version”,“description”,“type”,“script”,“checksum”,“installed_on”,“installed_by”,“execution_time”,“success” FROM “PUBLIC”.“schema_version” WHERE “installed_rank” > -1 ORDER BY “installed_rank”
  13. SET SCHEMA “PUBLIC”
  14. Connection.close()

schema_version というテーブルがないと create table しています。おそらくこのテーブルでどのバージョンの SQL まで実行済なのかを管理しているのでしょう。

“flyway schema_version” で Google で検索すると How Flyway works のページがヒットしました。中の記述を読むと確かに schema_version というテーブルでバージョン管理されていました。

IntelliJ IDEA の Database Tools から H2 Database へ接続する

IntelliJ IDEA の Database Tool Window の左上の「+」ボタンをクリックして「Data Source」-「H2」を選択します。

f:id:ksby:20170813194830p:plain

「Data Sources and Drivers」ダイアログが表示されますので、以下の値を入力して「OK」ボタンをクリックします。

f:id:ksby:20170813195119p:plain

  • URL に jdbc:h2:mem:bootnpmgebdb を入力します。
  • 「Test Connection」ボタンをクリックして “Successful” が表示されることを確認します。

f:id:ksby:20170813195330p:plain

  • 「Schemas」タブに切り替えて「All schemas」をチェックします。

Database Tool Window に戻ると「bootnpmgebdb」が追加されています。が、Flyway が PUBLIC の下に schema_version テーブルを作成したはずなのに何も表示されていませんね。。。?

f:id:ksby:20170813195555p:plain

H2 Console に接続する

H2 Database には H2 Console という Web アプリケーションがありますので、そちらに接続しても schema_version テーブルが見えないのか確認してみます。

H2 Console はデフォルトでは起動していないので、src/main/resources/application-develop.properties の最後に以下の設定を追加して develop 環境の時だけ起動するようにします。

spring.h2.console.enabled=true

Tomcat を再起動してからブラウザで http://localhost:8080/h2-console にアクセスすると、Basic 認証のダイアログ?が表示されました。これは Spring Security でしょうか?

f:id:ksby:20170813225043p:plain

Web で調べてみると以下の記事が見つかりました。

まずは Spring Security の Basic 認証を無効にします。src/main/resources/application-develop.properties に以下の設定を追加します。

spring.h2.console.enabled=true
security.basic.enabled=false
  • security.basic.enabled=false を追加します。

次に src/main/java/ksbysample/webapp/bootnpmgeb/config/WebSecurityConfig.java を以下のように変更します。

package ksbysample.webapp.bootnpmgeb.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import java.util.regex.Pattern;

/**
 * ???
 */
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private Pattern H2_CONSOLE_URI_PATTERN = Pattern.compile("^/h2-console");

    @Value("${spring.h2.console.enabled:false}")
    private boolean springH2ConsoleEnabled;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 認証の対象外にしたいURLがある場合には、以下のような記述を追加します
                // 複数URLがある場合はantMatchersメソッドにカンマ区切りで対象URLを複数列挙します
                // .antMatchers("/country/**").permitAll()
                //
                // この Web アプリケーションでは Spring Security を CSRF対策で使用したいだけなので、
                // 全ての URL を認証の対象外にする
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated();

        // spring.h2.console.enabled=true に設定されている場合には H2 Console を表示するために必要な設定を行う
        if (springH2ConsoleEnabled) {
            http.csrf()
                    .requireCsrfProtectionMatcher(request -> {
                        // H2 Console は CSRF対策の対象外にする
                        if (H2_CONSOLE_URI_PATTERN.matcher(request.getRequestURI()).lookingAt()) {
                            return false;
                        }
                        return true;
                    });
            http.headers().frameOptions().sameOrigin();
        }
    }

}
  • private Pattern H2_CONSOLE_URI_PATTERN = Pattern.compile("^/h2-console"); を追加します。
  • @Value("${spring.h2.console.enabled:false}") private boolean springH2ConsoleEnabled; を追加します。
  • configure メソッド内に if (springH2ConsoleEnabled) { ... } の処理を追加します。/h2-console の URI に対する CSRF対策を無効にし、X-Frame-Options レスポンスヘッダに DENY ではなく SAMEORIGIN がセットされるようにします。

再び Tomcat を再起動してからブラウザで http://localhost:8080/h2-console にアクセスすると今度はログイン画面が表示されました。

f:id:ksby:20170814003240p:plain

JDBC URL」に jdbc:h2:mem:bootnpmgebdb を、「User Name」に sa を入力して「Connect」ボタンをクリックします。

H2 Console の画面が表示されました。画面左側を見ると schema_version テーブルが表示されています。

f:id:ksby:20170814003458p:plain

Database Tools で接続しても schema_version テーブルが見えない原因がなんとなく分かりました。おそらく起動しているプロセスが別なので、見ている in-memory データベースが別々になっているからですね。

in-memory モードで起動している H2 Database に別プロセスから接続するには?

そんな方法があるのかな。。。と思いつつ Web で調べてみると、以下の記事が見つかりました。

これらの記事によると TCP サーバを起動して接続すればいいらしいです。試してみます。

src/main/java/ksbysample/webapp/bootnpmgeb/config の下に H2DatabaseConfig.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.config;

import org.h2.tools.Server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import java.sql.SQLException;

@Configuration
@Profile("develop")
public class H2DatabaseConfig {

    // TCP port for remote connections, default 9092
    @Value("${h2.tcp.port:9092}")
    private String h2TcpPort;

    /**
     * TCP connection to connect with SQL clients to the embedded h2 database.
     * Connect to "jdbc:h2:tcp://localhost:9092/mem:testdb", username "sa", password empty.
     */
    @Bean
    public Server h2TcpServer() throws SQLException {
        return Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", h2TcpPort).start();
    }

}
  • TCP サーバだけあればよいので、Web サーバは記述していません。
  • develop 環境でのみ起動します。

Tomcat を再起動します。

H2 Console から接続を試みてみます。「JDBC URL」に jdbc:h2:tcp://localhost:9092/mem:bootnpmgebdb と入力して「Connect」ボタンをクリックすると、

f:id:ksby:20170814072125p:plain

接続できました。scheme_version テーブルも表示されています。

f:id:ksby:20170814072245p:plain

再び Database Tools から H2 Database へ接続してみる

Database Tool Window から「Data Sources and Drivers」ダイアログを表示して、以下のように設定しますが。。。

f:id:ksby:20170815001348p:plain

  • 「URL」の右側のドロップダウンリストで「Remote」を選択した後、jdbc:h2:tcp://localhost:9092/mem:bootnpmgebdb を入力します。
  • 「User」に sa を入力します。
  • 「Test Connection」ボタンをクリックして “Successful” のメッセージが表示されることを確認します。

「Test Connection」は成功しているのに、ダイアログの下に「Error: [90067][90067]接続が壊れています: “unexpected status 256"」のメッセージが表示されます。「Database」のところに mem:bootnpmgebdb ではなく mem としか表示されていないからでしょうか。。。

「Schemas」タブをクリックすると何もスキーマが表示されていません。

f:id:ksby:20170815001746p:plain

「Data Sources and Drivers」ダイアログの設定をしばらく見ていたら「URL」の右側のドロップダウンリストに「URL only」という選択肢があることに気づきました。

f:id:ksby:20170815002414p:plain

「URL only」で設定すると今度は「URL」のところに赤縦線は表示されず「Test Connection」も成功しているのですが、「接続が壊れています」のメッセージが相変わらず表示されています。

f:id:ksby:20170815002557p:plain

原因がまるで分かりません。。。

Web でエラーメッセージからいろいろ調べてみたら以下のやり取りを見つけました。サーバとクライアントで H2 のバージョンが違うからではないか?、と書かれていますね。

確認してみます。Project Tool Window の「External Libraries」でサーバのバージョンを見ると 1.4.195 でした。

f:id:ksby:20170815003730p:plain

Database Tool Window から「Data Sources and Drivers」ダイアログを表示して H2 のクライアントのバージョンを見ると latest と表示されていますが、この表示だと使われているのは 1.4.192 でしょう。

f:id:ksby:20170815004126p:plain

確かに違いました。H2 のサーバのバージョンを 1.4.192 に変更して問題が解消されるか試してみます。build.gradle を以下のように変更します。

dependencies {
    ..........
    compile("org.codehaus.janino:janino")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    ..........

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    compile("com.integralblue:log4jdbc-spring-boot-starter:1.0.1")
    compile("org.flywaydb:flyway-core:4.2.0")
    compile("com.h2database:h2:1.4.192")
    testCompile("org.dbunit:dbunit:2.5.3")
    ..........
}
  • compile("com.h2database:h2") を下に移動して compile("com.h2database:h2:1.4.192") に変更します。

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

Tomcat を再起動した後、「Data Sources and Drivers」ダイアログを表示すると今度は「接続が壊れています」のメッセージが表示されていません。「Test Connection」も成功します。

f:id:ksby:20170815005353p:plain

「Schemas」タブをクリックするとスキーマ一覧も表示されています。「All schemas」をチェックします。

f:id:ksby:20170815071508p:plain

「OK」ボタンをクリックしてダイアログを閉じた後 Database Tool Window を見ると、今度は schema_version テーブルが表示されていました。接続できたようです。

f:id:ksby:20170815012438p:plain

履歴

2017/08/16
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( 番外編 )( browser-sync + http-proxy-middleware で https の環境を構築する )

概要

記事一覧はこちらです。

Spring Boot で開発中に https を使用したい場合、keytool コマンドで key-store を作成して application.properties の server.ssl.* に設定する方法がありますが、browser-sync + http-proxy-middleware を使っても https の環境を構築できるのでそのメモ書きです。

browser-sync の https 機能を利用すれば key-store を作成する必要がないのが便利です。

bs-springboot-config.js を変更して https 環境を構築する

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その14 )( browser-sync –> Tomcat 連携してファイル変更時に自動リロードで反映される環境を構築してみる ) で作成した bs-springboot-config.js の設定を変更して https 環境を構築してみます。

bs-springboot-config.js の以下の点を変更します。

var httpProxyMiddleware = require('http-proxy-middleware');
var proxy = httpProxyMiddleware(
    [
        // /css, /js, /vendor と *.html は Tomcat に転送しない
        "!/css/**/*",
        "!/js/**/*",
        "!/vendor/**/*",
        "!/**/*.html",
        "/**/*"
    ],
    {
        target: "http://localhost:8080",
        xfwd: true
    }
);

..........
module.exports = {
    ..........
    "serveStatic": [],
    "https": true,
    "ghostMode": {
        "clicks": true,
        "scroll": true,
        "location": true,
        "forms": {
            "submit": true,
            "inputs": true,
            "toggles": true
        }
    },
    ..........
  • xfwd: true を追加します。Tomcat 転送時に X-Forward 系の HTTP Header が付加されるようになります。
  • "https": true を追加します。

これだけです。尚、"https": true を設定すると https しか受け付けなくなります。

動作確認してみます。Tomcagt を起動した後、npm run springboot コマンドを実行します。browser-sync が起動すると https で待ち受けていることが分かります。

f:id:ksby:20170813062828p:plain

IE から https://localhost:9080/inquiry/input/01/ にアクセスすると、自己証明書なので以下の画面が表示されます。「このサイトの閲覧を続行する (推奨されません)。」リンクをクリックします。

f:id:ksby:20170813063102p:plain

入力画面1が表示されます。自己証明書なのでアドレスバーが赤く表示されています。

f:id:ksby:20170813063256p:plain

「次へ」ボタンを押すと入力画面2へ遷移します。リダイレクトが行われていますが、プロトコルhttps)やドメイン名(localhost:9080)は引き継がれています。

f:id:ksby:20170813063437p:plain

ちなみに bs-springboot-config.js に xfwd: true を設定しないと入力画面1→入力画面2へ遷移しようとすると以下のようにプロトコルhttps)が引き継がれません。

f:id:ksby:20170813063742p:plain f:id:ksby:20170813063836p:plain

http を 80番ポートで、https を 443番ポートで待ち受ける環境を構築するには?

browser-sync の設定ファイルを2つ作って、2つ同時に起動すれば構築可能です。

先程の bs-springboot-config.js の設定変更を元に戻してから、以下のように変更します。

module.exports = {
    ..........
    "proxy": false,
    "port": 80,
    "middleware": false,
    ..........
  • "port": 9080"port": 80 に変更します。

bs-springboot-config.js をコピーして bs-springboot-https-config.js を作成し、以下の内容に変更します。

var httpProxyMiddleware = require('http-proxy-middleware');
var proxy = httpProxyMiddleware(
    [
        // /css, /js, /vendor と *.html は Tomcat に転送しない
        "!/css/**/*",
        "!/js/**/*",
        "!/vendor/**/*",
        "!/**/*.html",
        "/**/*"
    ],
    {
        target: "http://localhost:8080",
        xfwd: true
    }
);

/*
 |--------------------------------------------------------------------------
 | Browser-sync config file
 |--------------------------------------------------------------------------
 |
 | For up-to-date information about the options:
 |   http://www.browsersync.io/docs/options/
 |
 | There are more options than you see here, these are just the ones that are
 | set internally. See the website for more info.
 |
 |
 */
module.exports = {
    "ui": false,
    "files": [
        "./build/classes/**/*.class",
        "./build/classes/**/*.html",
        "./static/**/*",
        "./src/main/resources/static/**/*"
    ],
    "watchEvents": [
        "change"
    ],
    "watchOptions": {
        "ignoreInitial": true,
        "ignorePermissionErrors": true
    },
    "server": {
        "baseDir": [
            "./static",
            "./src/main/resources/static"
        ],
        "middleware": [proxy]
    },
    "proxy": false,
    "port": 443,
    "middleware": false,
    "serveStatic": [],
    "https": true,
    "ghostMode": {
        "clicks": true,
        "scroll": true,
        "location": true,
        "forms": {
            "submit": true,
            "inputs": true,
            "toggles": true
        }
    },
  • xfwd: true を追加します。
  • "ui": {"port": 3001, "weinre": {"port": 9081}}"ui": false に変更します。ui を起動しないようにします。
  • "port": 9080"port": 443 に変更します。
  • "https": true を追加します。
  • "open": "local""open": false に変更します。browser-sync 起動時にブラウザを起動しないようにします。

package.json を以下のように変更します。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "run-s clean:static-dir copy:all",
    "clean:static-dir": "rimraf src/main/resources/static/*",
    "copy:all": "run-p copy:bootstrap copy:admin-lte copy:font-awesome copy:ionicons",
    "copy:bootstrap": "cpx node_modules/bootstrap/dist/**/* src/main/resources/static/vendor/bootstrap",
    "copy:admin-lte": "cpx node_modules/admin-lte/dist/**/* src/main/resources/static/vendor/admin-lte",
    "copy:font-awesome": "cpx node_modules/font-awesome/{css,fonts}/**/* src/main/resources/static/vendor/font-awesome",
    "copy:ionicons": "cpx node_modules/ionicons/dist/{css,fonts}/**/* src/main/resources/static/vendor/ionicons",
    "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll",
    "webpack": "webpack",
    "webpack:watch": "webpack --watch",
    "browser-sync": "browser-sync",
    "browser-sync:start": "browser-sync start --config bs-config.js",
    "browser-sync:springboot": "browser-sync start --config bs-springboot-config.js",
    "browser-sync:springboot:https": "browser-sync start --config bs-springboot-https-config.js",
    "server": "run-p postcss:watch webpack:watch browser-sync:start",
    "springboot": "run-p postcss:watch webpack:watch browser-sync:springboot browser-sync:springboot:https"
  },
  • "browser-sync:springboot:https": "browser-sync start --config bs-springboot-https-config.js" を追加します。
  • "springboot" の最後に browser-sync:springboot:https を追加します。

Tomcat を起動します。

コマンドプロンプトを「管理者として実行…」で起動した後、npm run springboot コマンドを実行します。http で 80番、https で 443番で待ち受けていることが表示されます。

f:id:ksby:20170813091832p:plain

ブラウザを起動して http://localhost/inquiry/input/01/ にアクセスして入力画面1を表示してから、「次へ」ボタンをクリックすると入力画面2が表示されます。URL は http://localhost/inquiry/input/02/ になっています。

f:id:ksby:20170813092005p:plain f:id:ksby:20170813092215p:plain

https://localhost/inquiry/input/01/ にアクセスして入力画面1を表示してから、「次へ」ボタンをクリックすると入力画面2が表示されます。URL は https://localhost/inquiry/input/02/ になっています。

f:id:ksby:20170813092329p:plain f:id:ksby:20170813092424p:plain

localhost ではなくドメイン名(www.test.co.jp)にしても問題ないか確認してみます。hosts ファイルに以下の定義を追加します。192.168.56.1 は自分の PC に割り当てられている IPアドレスです。

192.168.56.1    www.test.co.jp

http://www.test.co.jp/inquiry/input/01/ にアクセスして入力画面1を表示してから、「次へ」ボタンをクリックすると入力画面2が表示されます。URL は http://www.test.co.jp/inquiry/input/02/ になっています。

f:id:ksby:20170813093033p:plain f:id:ksby:20170813093123p:plain

https://www.test.co.jp/inquiry/input/01/ にアクセスして入力画面1を表示してから、「次へ」ボタンをクリックすると入力画面2が表示されます。URL は https://www.test.co.jp/inquiry/input/02/ になっています。

f:id:ksby:20170813093259p:plain f:id:ksby:20170813093343p:plain

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その14 )( browser-sync --> Tomcat 連携してファイル変更時に自動リロードで反映される環境を構築してみる )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その13 )( HTML を Thymeleaf テンプレートファイルにする + Controller クラスを作成する2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • browser-sync –> Tomcat 連携して、ファイルを変更したらブラウザ側で自動リロードして反映される環境を構築できるか試してみます。
    • 尚、Tomcat の起動は JRebel で行っています。JRebel は personal, non-commercial use only なら free な myJRebel があります。
    • devltools + bootRun タスクで Tomcat を起動したらどうなるかも まとめ に書きました。
    • 結論を言うと完全自動リロードする環境は構築可能でしたが、その場合無駄な動作をして開発効率が上がる気がしなかったので、Thymeleaf テンプレートファイルや java ファイルは手動 build することにしました。

参照したサイト・書籍

  1. Spring Boot + Gradle on Intellij IDEA でアプリケーション実行中にコードの変更反映と Thymeleaf テンプレートの変更反映
    http://bufferings.hatenablog.com/entry/2017/07/24/010551

  2. Gulp browser-sync - redirect API request via proxy
    https://stackoverflow.com/questions/25410284/gulp-browser-sync-redirect-api-request-via-proxy

  3. http-proxy-middleware
    https://www.npmjs.com/package/http-proxy-middleware

  4. chimurai/http-proxy-middleware - Impossible to ignore proxy path prefix
    https://github.com/chimurai/http-proxy-middleware/issues/31

  5. Browsersync options
    https://browsersync.io/docs/options

  6. Chokidar
    https://github.com/paulmillr/chokidar

目次

  1. browser-sync の proxy 機能で Tomcat と連携してみる
    1. bs-springboot-config.js を新規作成する
    2. package.json に起動用の npm-scripts を追加する
    3. 自動リロードされるか確認する(js ファイル編)
    4. 自動リロードされるか確認する(Thymeleaf テンプレートファイル編)
    5. 自動リロードされるか確認する(class ファイル編)
    6. ここまでの結果をまとめる
  2. Tomcat 起動中でも自動 build されるよう IntelliJ IDEA の設定を変更して、自動リロードされるか確認する
    1. Tomcat 起動中でも自動 build されるよう IntelliJ IDEA の設定を変更する
    2. 自動リロードされるか確認する(Thymeleaf テンプレートファイル編)
    3. 自動リロードされるか確認する(class ファイル編)
    4. 結果をまとめる
  3. js ファイルは Tomcat からではなく browser-sync から直接返すようにしてみる
    1. 特定の URL だけ Tomcat へ転送せずに browser-sync から直接返すことができるのか?
    2. http-proxy-middleware をインストールする
    3. bs-springboot-config.js を変更する
    4. 自動リロードされるか確認する(js ファイル編)
  4. build 時に browser-sync が落ちないように watchOptions を設定する
  5. まとめ

手順

browser-sync の proxy 機能で Tomcat と連携してみる

browser-sync には proxy 機能がありますので、リクエストを全て Tomcat へ転送して自動リロードされる環境を構築できるか試してみます。

bs-springboot-config.js を新規作成する

bs-config.js をコピーして bs-springboot-config.js を作成した後、以下の点を変更します。

/*
 |--------------------------------------------------------------------------
 | Browser-sync config file
 |--------------------------------------------------------------------------
 |
 | For up-to-date information about the options:
 |   http://www.browsersync.io/docs/options/
 |
 | There are more options than you see here, these are just the ones that are
 | set internally. See the website for more info.
 |
 |
 */
module.exports = {
    "ui": {
        "port": 3001,
        "weinre": {
            "port": 9081
        }
    },
    "files": [
        "./build/classes/**/*"
    ],
    "watchEvents": [
        "change"
    ],
    "watchOptions": {
        "ignoreInitial": true
    },
    "server": false,
    "proxy": "localhost:8080",
    "port": 9080,
    ..........
  • Tomcat は build/classes の下のファイルを見ているようなので、このディレクトリの下のファイルの変更を監視します。files の設定を "files": [ "./build/classes/**/*" ] に変更します。
  • browser-sync からは直接ファイルを返信しなくなるので、server の設定を "server": false に変更します。
  • "proxy": false"proxy": "localhost:8080" に変更します。

package.json に起動用の npm-scripts を追加する

package.json に bs-springboot-config.js を使用して browser-sync を起動するための npm-scripts を追加します。

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "run-s clean:static-dir copy:all",
    "clean:static-dir": "rimraf src/main/resources/static/*",
    "copy:all": "run-p copy:bootstrap copy:admin-lte copy:font-awesome copy:ionicons",
    "copy:bootstrap": "cpx node_modules/bootstrap/dist/**/* src/main/resources/static/vendor/bootstrap",
    "copy:admin-lte": "cpx node_modules/admin-lte/dist/**/* src/main/resources/static/vendor/admin-lte",
    "copy:font-awesome": "cpx node_modules/font-awesome/{css,fonts}/**/* src/main/resources/static/vendor/font-awesome",
    "copy:ionicons": "cpx node_modules/ionicons/dist/{css,fonts}/**/* src/main/resources/static/vendor/ionicons",
    "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll",
    "webpack": "webpack",
    "webpack:watch": "webpack --watch",
    "browser-sync": "browser-sync",
    "browser-sync:start": "browser-sync start --config bs-config.js",
    "browser-sync:springboot": "browser-sync start --config bs-springboot-config.js",
    "server": "run-p postcss:watch webpack:watch browser-sync:start",
    "springboot": "run-p postcss:watch webpack:watch browser-sync:springboot"
  },
  • 以下の2行を追加します。
    • "browser-sync:springboot": "browser-sync start --config bs-springboot-config.js"
    • "springboot": "run-p postcss:watch webpack:watch browser-sync:springboot"

これで npm run springboot コマンドを実行すれば browser-sync –> Tomcat 連携するようになります。

自動リロードされるか確認する(js ファイル編)

Tomcat を起動してから npm run springboot コマンドを実行します。

ブラウザから http://localhost:9080/inquiry/input/01/ にアクセスして入力画面1を表示した後(Tomcat ではなく browser-sync にアクセスするのでポート番号は 8080 ではなく bs-springboot-config.js に設定している 9080 になります)、js ファイル、Thymeleaf テンプレート、class ファイルを更新してみます。

まずは js ファイルから。src/main/assets/js/inquiry/input01.js を以下のように変更します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    $("h1").text("テストです");

    // 動作確認のために初期表示時に「次へ」ボタンをクリック可能にする
    $(".js-btn-next").prop("disabled", false);

    $(".js-btn-next").on("click", function (event) {
        $("#input01Form").attr("action", "/inquiry/input/01/?move=next");
        $("#input01Form").submit();

        // return false は
        // event.preventDefault() + event.stopPropagation() らしい
        return false;
    })
});
  • $("h1").text("テストです"); を追加します。

webpack が変更を検知して src/main/resources/static/js/inquiry/input01.js を出力しますが、

f:id:ksby:20170811205629p:plain

ブラウザに表示している入力画面1のタイトルは変更されませんでした。自動で build/classes/main/static/js/inquiry/input01.js の方に変更が反映されないので当然でした。

f:id:ksby:20170811205811p:plain

反映するために Ctrl+F9 を押して build したら npm run springboot コマンドが落ちました。。。 IntelliJ IDEA の方で Parsing java ... が表示されて数秒経過しているうちに npm run springboot コマンドがエラーで落ちるようです。何度試してみても Parsing java ... が表示されるとダメでした。js ファイルは webpack が出力するので Ctrl+Shift+F9 でそのファイルだけ build するという訳にもいきませんし、どうしたものでしょうか。。。

自動リロードされるか確認する(Thymeleaf テンプレートファイル編)

次は Thymeleaf テンプレートファイルで試してみます。npm run springboot コマンドを実行し直して、ブラウザでは入力画面1を表示しておきます。

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

    <section class="content-header">
      <h1>
        テストです2
      </h1>
    </section>
  • h1 タグ内のテキストを 入力画面1テストです2 に変更します。

この時点でブラウザに切り替えても入力画面1の画面名の部分は何も変わりませんでした。何もしなければ Thymeleaf テンプレートファイルの変更は build/classes/main/templates/web/inquiry/input01.html に反映されないので当然でした。

Ctrl+F9 を押して build します。Reloading Browsers... のメッセージがいくつも出力されますが、

f:id:ksby:20170812074954p:plain

自動リロードされて画面名が テスト2 に変わりました。

f:id:ksby:20170812075104p:plain

この後何度か画面名を変更して Ctrl+F9 を押してみましたが、常に反映されました。Ctrl+F9 を押す手間はありますが、変更検知してブラウザに反映されます。

自動リロードされるか確認する(class ファイル編)

最後に class ファイルで試してみます。npm run springboot コマンドを実行し直して、ブラウザでは入力画面1を表示しておきます。

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java を以下のように変更します。

    /**
     * 入力画面1 初期表示処理
     *
     * @return 入力画面1の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping("/01")
    public String input01() {
        return TEMPLATE_INPUT02;
    }
  • TEMPLATE_INPUT01TEMPLATE_INPUT02 に変更します。

IntelliJ IDEA では通常自動コンパイルされないので、この時点では画面は何も変わりません。

Ctrl+F9 を押して build します。大量の Reloading Browsers... が出力されますが、

f:id:ksby:20170812080832p:plain f:id:ksby:20170812080947p:plain f:id:ksby:20170812081056p:plain f:id:ksby:20170812081220p:plain f:id:ksby:20170812081321p:plain

自動リロードされて入力画面2の画面が表示されました。

f:id:ksby:20170812081429p:plain

Controller クラスが返す画面を 入力画面1→入力画面2→入力画面1→… と何度か変更して Ctrl+F9 を押してみましたが、browser-sync は落ちることなくブラウザの画面に反映されました。Ctrl+F9 を押す手間がありますが、こちらも変更検知してブラウザに反映されるようです。

ここまでの結果をまとめる

現時点の状況としては、

  • js ファイルは、変更した後で js ファイルからカーソルを外せば webpack が変更検知して出力し直してくれますが、その後に自動で Tomcat が見ている build/classes ディレクトリに反映されないので自動リロードされません。また Ctrl+F9 を押して build/classes の下に反映しても browser-sync が落ちてしまいます。
  • Thymeleaf テンプレートファイルと class ファイルは Ctrl+F9 を押す手間は必要ですが、build するとブラウザには自動リロードされて反映されます。Tomcat 起動中も自動 build されるよう設定すればよさそう?

Tomcat 起動中でも自動 build されるよう IntelliJ IDEA の設定を変更して、自動リロードされるか確認する

Tomcat 起動中でも自動 build されるよう IntelliJ IDEA の設定を変更する

Tomcat を起動していない場合に自動 build する方法は知っているのですが Tomcat 起動中でも自動 build する方法がないか調べたところ、Spring Boot + Gradle on Intellij IDEA でアプリケーション実行中にコードの変更反映と Thymeleaf テンプレートの変更反映 という記事を見つけました。ちょうどやりたいことと一致していたので、この記事の通り設定してみます。

IntelliJ IDEA のメインメニューから「File」-「Settings…」を選択します。

「Settings」ダイアログが表示されたら、画面左側の一覧から「Build, Execution, Deployment」-「Compiler」を選択した後、画面右側の「Build project automatically」をチェックします。チェック後「OK」ボタンをクリックしてダイアログを閉じます。

f:id:ksby:20170812093132p:plain

次に IntelliJ IDEA のメインメニューから「Help」-「Find Action…」を選択します。

「Enter action or option name」ダイアログが表示されますので、registry と入力して、表示された一覧から Registry... を選択します。

f:id:ksby:20170812093421p:plain

「Registry」ダイアログが表示されますので「compiler.automake.allow.when.app.running」をチェックします。チェック後「Close」ボタンをクリックしてダイアログを閉じます。

f:id:ksby:20170812094401p:plain

自動リロードされるか確認する(Thymeleaf テンプレートファイル編)

設定は以上で終了です。今度は Ctrl+F9 を押すことなしに自動リロードされるか試してみます。

Tomcat を起動してから npm run springboot コマンドを実行し、ブラウザで入力画面1を表示します。

先程と同様に src/main/resources/templates/web/inquiry/input01.html の h1 のテキストを テストです2 に変更します。が、ブラウザに反映されません。また Thymeleaf を編集しようとした時に、先程 java ファイルを変更して Ctrl+F9 を押して build した時と同様に大量の Reloading Browsers... のメッセージが出力されました。

Ctrl+F9 を押さなくても自動 build されて build/classes/main/templates/web/inquiry/input01.html に変更は反映されていたのですが、browser-sync が変更を検知できないようです。ブラウザで F5 を押して手動リロードすると画面上に変更が反映されました。

自動リロードされるか確認する(class ファイル編)

今度は src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry/InquiryInputController.java の input01 メソッドの戻り値を TEMPLATE_INPUT01TEMPLATE_INPUT02 に変更してみます。

こちらは自動でブラウザまで反映されました。ただしリロードが2回発生します。ブラウザのタブに表示される文字列を見ていると、1回目は入力画面1で、2回目は入力画面2でした。何度か試しても同じです。またリロードが発生するのは IntelliJ IDEA 内で別のファイルにタブを切り替えるか、別のプロセスに切り替える必要がありました。自動 build は編集しているファイルにカーソルがある間は実行されないようです。

f:id:ksby:20170812102639p:plain

結果をまとめる

IntelliJ IDEA で自動 build の設定をすると class ファイルの方は自動リロードされますが、Thymeleaf テンプレートファイルの方は手動リロードが必須でした。また少し多めにリロードが発生するのが気になりました。一旦自動 build の設定は元に戻します。

js ファイルは Tomcat からではなく browser-sync から直接返すようにしてみる

特定の URL だけ Tomcat へ転送せずに browser-sync から直接返すことができるのか?

js ファイルを変更すれば webpack の watch 機能で変更検知されて自動で src/main/resources/static/js の下に出力されることは既に分かっており、build/classes/main/static の下のファイルは src/main/resources/static の下のファイルをコピーしているだけであること、src/main/resources/static の下のファイルが変更されたら browser-sync が変更検知して自動リロードしてくれることは既に確認していることから、js ファイルは Tomcat へ転送せずに browser-sync から直接返すようにすれば Ctrl+F9 を押す手間がなくなるのではないか?、と思いました。

browser-sync でそのような設定が可能なのか調べてみたところ、以下の記事を見つけました。browser-sync の proxy 機能は使用せずに、http-proxy-middleware パッケージを使用すれば実現できそうなので、試してみることにします。

http-proxy-middleware をインストールする

npm install --save-dev http-proxy-middleware コマンドを実行してインストールします。

f:id:ksby:20170812150752p:plain

bs-springboot-config.js を変更する

bs-springboot-config.js の以下の点を変更します。

var httpProxyMiddleware = require('http-proxy-middleware');
var proxy = httpProxyMiddleware(
    [
        // /css, /js, /vendor と *.html は Tomcat に転送しない
        "!/css/**/*",
        "!/js/**/*",
        "!/vendor/**/*",
        "!/**/*.html",
        "/**/*"
    ],
    {target: "http://localhost:8080"}
);

/*
 |--------------------------------------------------------------------------
 | Browser-sync config file
 |--------------------------------------------------------------------------
 |
 | For up-to-date information about the options:
 |   http://www.browsersync.io/docs/options/
 |
 | There are more options than you see here, these are just the ones that are
 | set internally. See the website for more info.
 |
 |
 */
module.exports = {
    "ui": {
        "port": 3001,
        "weinre": {
            "port": 9081
        }
    },
    "files": [
        "./build/classes/**/*.class",
        "./build/classes/**/*.html",
        "./static/**/*",
        "./src/main/resources/static/**/*"
    ],
    "watchEvents": [
        "change"
    ],
    "watchOptions": {
        "ignoreInitial": true
    },
    "server": {
        "baseDir": [
            "./static",
            "./src/main/resources/static"
        ],
        "middleware": [proxy]
    },
    "proxy": false,
    "port": 9080,
    ..........
  • ファイルの先頭に以下の2行を追加します。
    • var httpProxyMiddleware = require('http-proxy-middleware');
    • var proxy = httpProxyMiddleware(["!/css/**/*", "!/js/**/*", "!/vendor/**/*", "!/**/*.html", "/**/*"], {target: "http://localhost:8080"});
  • "files": ["./build/classes/**/*"]"files": ["./build/classes/**/*.class", "./build/classes/**/*.html", "./static/**/*", "./src/main/resources/static/**/*"] に変更します。
  • "server": false"server": {"baseDir": ["./static", "./src/main/resources/static"], "middleware": [proxy]} に変更します。
  • "proxy": "localhost:8080""proxy": false に変更します。

自動リロードされるか確認する(js ファイル編)

js ファイル変更時に自動リロードされるか試してみます。

Tomcat を起動してから npm run springboot コマンドを実行し、ブラウザで入力画面1を表示します。

src/main/assets/js/inquiry/input01.js に $("h1").text("テストです"); を追加します。ブラウザに切り替えると画面名が テストです にすぐに切り替わりました。

f:id:ksby:20170812155952p:plain

ただしこの後に Ctrl+F9 を押して build し、Parsing java ... のメッセージが数秒出る状態になると browser-sync が落ちました。。。 コマンドプロンプトに出力されているエラーメッセージを見ると Error: EPERM: operation not permitted, lstat 'c:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\build\classes\main\static\js\inquiry' と出力されています。おそらく build 時に lstat できなくなる場合があって、それに引っかかるとエラーで落ちてしまうものと思われます。

build 時に browser-sync が落ちないように watchOptions を設定する

build 時に browser-sync が落ちてしまうのを回避できる設定がないか Browsersync options のページを見たのですが、「watchOptions」のところに File watching options that get passed along to Chokidar. Check their docs for available options という記述と Chokidar へのリンクがありました。

Chokidar のページを見てみると ignorePermissionErrors という設定がありました。この項目の説明文に EPERM の記述があったので、おそらくこの設定でエラーを無視できると思われます。デフォルトでは false なので true にしてみます。

Tomcat を起動してから npm run springboot コマンドを実行し、ブラウザで入力画面1を表示します。

src/main/assets/js/inquiry/input01.js に $("h1").text("テストです"); を追加してから Ctrl+F9 を押して build を実行し Parsing java ... のメッセージが数秒出る状態を何度か再現してみましたが、今度は browser-sync がエラーで落ちることはありませんでした。

また Chokidar のページには usePolling というオプションもあり、ファイルの変更をポーリングでチェックできるようでした。IntelliJ IDEA で Tomcat 起動時にも自動 build できるように設定を変更して、browser-sync の watchOptions に "usePolling": true を追加してみたところ、Thymeleaf テンプレートファイルを変更したときも class ファイルを変更したときもどちらも自動リロードさせることができたのですが、class ファイル変更時に自動リロードが2回動くことと、js ファイルの時と比較するとあまり動きが軽快でないことが自分好みではなかったので、このオプションは使用しないことにします。usePolling を使用しなくても Ctrl+F9 か Ctrl+Shift+F9(変更したファイルだけ build)で手動 build すれば自動リロードされるので、build は手動で行うことにします。

まとめ

  • browser-sync –> Tomcat 連携は可能。ただし Tomcat は JRebel で起動すること。使い勝手は申し分ない。
  • browser-sync –> Tomcat 連携は browser-sync の proxy 機能は使用せず http-proxy-middleware パッケージを使用する。
  • http-proxy-middleware パッケージを使用する理由は /css, /js 及び *.html にマッチする URL の場合には Tomcat に転送せず browser-proxy から直接返信するためである。その方が js ファイル変更 –> ブラウザの自動リロードまでのレスポンスが軽快で開発しやすい。
  • Thymeleaf テンプレートファイルと class ファイルは Tomcat から返信する。
  • IntelliJ IDEA の自動 build の設定は有効にしない。Thymeleaf テンプレートファイルや java ファイルを変更した時には Ctrl+F9 あるいは Ctrl+Shift+F9 で手動 build する。手動 build すると browser-sync がファイルの変更を検知してブラウザを自動リロードする。
  • browser-sync, Tomcat はどちらから起動してもよい。また途中でどちらかを再起動しても問題ない。
  • Tomcat を devtools + bootRun タスクで起動した場合、class ファイル変更時には自動リロードで反映されない。devtools がクラスローダーを再起動する時間が長く、class ファイルの変更を検知して自動リロードしようとしても Tomcat からの応答がタイムアウトして Error occured while trying to proxy to: localhost:9080/inquiry/input/01/ のようなエラーメッセージがブラウザ上に表示される。

最後に最終版の package.json と bs-springboot-config.js を載せておきます。

ソースコード

package.json

{
  "name": "boot-npm-geb-sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "run-s clean:static-dir copy:all",
    "clean:static-dir": "rimraf src/main/resources/static/*",
    "copy:all": "run-p copy:bootstrap copy:admin-lte copy:font-awesome copy:ionicons",
    "copy:bootstrap": "cpx node_modules/bootstrap/dist/**/* src/main/resources/static/vendor/bootstrap",
    "copy:admin-lte": "cpx node_modules/admin-lte/dist/**/* src/main/resources/static/vendor/admin-lte",
    "copy:font-awesome": "cpx node_modules/font-awesome/{css,fonts}/**/* src/main/resources/static/vendor/font-awesome",
    "copy:ionicons": "cpx node_modules/ionicons/dist/{css,fonts}/**/* src/main/resources/static/vendor/ionicons",
    "postcss:watch": "postcss src/main/assets/css/**/* -d src/main/resources/static/css -x .min.css -w --poll",
    "webpack": "webpack",
    "webpack:watch": "webpack --watch",
    "browser-sync": "browser-sync",
    "browser-sync:start": "browser-sync start --config bs-config.js",
    "browser-sync:springboot": "browser-sync start --config bs-springboot-config.js",
    "server": "run-p postcss:watch webpack:watch browser-sync:start",
    "springboot": "run-p postcss:watch webpack:watch browser-sync:springboot"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "admin-lte": "^2.3.11",
    "bootstrap": "^3.3.7",
    "font-awesome": "^4.7.0",
    "ionicons": "^3.0.0"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "browser-sync": "^2.18.13",
    "cpx": "^1.5.0",
    "cssnano": "^3.10.0",
    "http-proxy-middleware": "^0.17.4",
    "npm-run-all": "^4.0.2",
    "postcss-cli": "^4.1.0",
    "rimraf": "^2.6.1",
    "stylelint": "^8.0.0",
    "stylelint-config-standard": "^17.0.0",
    "webpack": "^3.3.0"
  }
}

bs-springboot-config.js

var httpProxyMiddleware = require('http-proxy-middleware');
var proxy = httpProxyMiddleware(
    [
        // /css, /js, /vendor と *.html は Tomcat に転送しない
        "!/css/**/*",
        "!/js/**/*",
        "!/vendor/**/*",
        "!/**/*.html",
        "/**/*"
    ],
    {target: "http://localhost:8080"}
);

/*
 |--------------------------------------------------------------------------
 | Browser-sync config file
 |--------------------------------------------------------------------------
 |
 | For up-to-date information about the options:
 |   http://www.browsersync.io/docs/options/
 |
 | There are more options than you see here, these are just the ones that are
 | set internally. See the website for more info.
 |
 |
 */
module.exports = {
    "ui": {
        "port": 3001,
        "weinre": {
            "port": 9081
        }
    },
    "files": [
        "./build/classes/**/*.class",
        "./build/classes/**/*.html",
        "./static/**/*",
        "./src/main/resources/static/**/*"
    ],
    "watchEvents": [
        "change"
    ],
    "watchOptions": {
        "ignoreInitial": true,
        "ignorePermissionErrors": true
    },
    "server": {
        "baseDir": [
            "./static",
            "./src/main/resources/static"
        ],
        "middleware": [proxy]
    },
    "proxy": false,
    "port": 9080,
    "middleware": false,
    "serveStatic": [],
    "ghostMode": {
        "clicks": true,
        "scroll": true,
        "location": true,
        "forms": {
            "submit": true,
            "inputs": true,
            "toggles": true
        }
    },
    "logLevel": "info",
    "logPrefix": "Browsersync",
    "logConnections": false,
    "logFileChanges": true,
    "logSnippet": true,
    "rewriteRules": [],
    "open": "local",
    "browser": "default",
    "cors": false,
    "xip": false,
    "hostnameSuffix": false,
    "reloadOnRestart": false,
    "notify": false,
    "scrollProportionally": true,
    "scrollThrottle": 0,
    "scrollRestoreTechnique": "window.name",
    "scrollElements": [],
    "scrollElementMapping": [],
    "reloadDelay": 0,
    "reloadDebounce": 0,
    "reloadThrottle": 0,
    "plugins": [],
    "injectChanges": true,
    "startPath": null,
    "minify": true,
    "host": null,
    "localOnly": false,
    "codeSync": true,
    "timestamps": true,
    "clientEvents": [
        "scroll",
        "scroll:element",
        "input:text",
        "input:toggles",
        "form:submit",
        "form:reset",
        "click"
    ],
    "socket": {
        "socketIoOptions": {
            "log": false
        },
        "socketIoClientConfig": {
            "reconnectionAttempts": 50
        },
        "path": "/browser-sync/socket.io",
        "clientPath": "/browser-sync",
        "namespace": "/browser-sync",
        "clients": {
            "heartbeatTimeout": 50000
        }
    },
    "tagNames": {
        "less": "link",
        "scss": "link",
        "css": "link",
        "jpg": "img",
        "jpeg": "img",
        "png": "img",
        "svg": "img",
        "gif": "img",
        "js": "script"
    }
};

履歴

2017/08/12
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その13 )( HTML を Thymeleaf テンプレートファイルにする + Controller クラスを作成する2 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その12 )( HTML を Thymeleaf テンプレートファイルにする + Controller クラスを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 前回からの続きです。今回は確認画面、完了画面の Thymeleaf テンプレートファイルと Controller クラスを実装します。

参照したサイト・書籍

目次

  1. 確認画面の Controller クラスを作成する
  2. 完了画面の Controller クラスを作成する
  3. Thymeleaf テンプレートファイルを変更する
  4. 画面遷移用の js ファイルを実装する
  5. 動作確認
  6. 次回は。。。

手順

確認画面の Controller クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryConfirmController.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry;

import ksbysample.webapp.bootnpmgeb.constants.UrlConst;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * 確認画面用 Controller クラス
 */
@Controller
@RequestMapping("/inquiry/confirm")
public class InquiryConfirmController {

    private static final String TEMPLATE_BASE = "web/inquiry";
    private static final String TEMPLATE_CONFIRM = TEMPLATE_BASE + "/confirm";

    /**
     * 確認画面 初期表示処理
     *
     * @return 確認画面の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping
    public String index() {
        return TEMPLATE_CONFIRM;
    }

    /**
     * 確認画面 「送信する」ボタンクリック時の処理
     *
     * @return 完了画面の URL
     */
    @PostMapping("/send")
    public String send(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_COMPLETE).toUriString();
    }

}

完了画面の Controller クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryCompleteController.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 完了画面用 Controller クラス
 */
@Controller
@RequestMapping("/inquiry/complete")
public class InquiryCompleteController {

    private static final String TEMPLATE_BASE = "web/inquiry";
    private static final String TEMPLATE_COMPLETE = TEMPLATE_BASE + "/complete";

    /**
     * 完了画面 初期表示処理
     *
     * @return 完了画面の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping
    public String index() {
        return TEMPLATE_COMPLETE;
    }

}

Thymeleaf テンプレートファイルを変更する

完了画面は js ファイルを作成する必要がなかったので、以下の点を変更します。

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 完了画面</title>
</head>

<body class="skin-blue layout-top-nav">
<div class="wrapper">

  <!-- Content Wrapper. Contains page content -->
  <div class="content-wrapper">
    <!-- Content Header (Page header) -->
    <section class="content-header">
      <h1>
        完了画面
      </h1>
    </section>

    <!-- Main content -->
    <section class="content">
      <div class="row">
        <div class="col-xs-12">
          <div class="text-center">
            <p>お問い合わせありがとうございました。</p>
            <a href="/inquiry/input/01/"><button class="btn bg-green"><i class="fa fa-reply"></i> 入力画面へ</button></a>
          </div>
        </div>
      </div>
    </section>
    <!-- /.content -->
  </div>
  <!-- /.content-wrapper -->
</div>
<!-- ./wrapper -->

<!-- REQUIRED JS SCRIPTS -->

</body>
</html>
  • 「入力画面へ」ボタンのリンクを <a href="/inquiry/input01.html"><a href="/inquiry/input/01/"> へ変更します。
  • <script src="/js/inquiry/complete.js"></script> を削除します。

画面遷移用の js ファイルを実装する

webpack.config.js を以下のように変更します。

module.exports = {
    entry: {
        "js/app": ["./src/main/assets/js/app.js"],
        "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
        "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"],
        "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"],
        "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"]
    },
    output: {
        path: __dirname + "/src/main/resources/static",
        publicPath: "/",
        filename: "[name].js"
    }
};
  • "js/inquiry/confirm": ["./src/main/assets/js/inquiry/confirm.js"] を追加します。

js ファイル実装前に npm run server コマンドを実行します。起動していた場合には一旦終了させてから実行し直します。

src/main/assets/js/inquiry の下に confirm.js を新規作成し、以下の内容を記述します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    $(".js-btn-input01").on("click", function (event) {
        location.href = "/inquiry/input/01/";
        return false;
    });

    $(".js-btn-input02").on("click", function (event) {
        location.href = "/inquiry/input/02/";
        return false;
    });

    $(".js-btn-input03").on("click", function (event) {
        location.href = "/inquiry/input/03/";
        return false;
    });

    $(".js-btn-send").on("click", function (event) {
        $("#confirmForm").attr("action", "/inquiry/confirm/send/");
        $("#confirmForm").submit();
        return false;
    });
});

動作確認

Tomcat を起動します。

ブラウザを起動し http://localhost:8080/inquiry/input/03/ にアクセスして入力画面3を表示します。

f:id:ksby:20170811133503p:plain

「確認画面へ」ボタンをクリックすると確認画面へ遷移します。アドレスバーに表示される URL は http://localhost:8080/inquiry/confirm/ に変わっています。

f:id:ksby:20170811133748p:plain

「送信する」ボタンをクリックすると完了画面へ遷移します。アドレスバーに表示される URL は http://localhost:8080/inquiry/complete/ に変わっています。

f:id:ksby:20170811133943p:plain

「入力画面へ」ボタンをクリックすると入力画面1へ遷移すること、確認画面で「修正する」ボタンをクリックすると入力画面1~3の画面へ遷移することも確認できました。

npm run server コマンド、Tomcat を停止します。

次回は。。。

  • browser-sync の自動リロードような利便性を Tomcat で起動した時にも欲しいなあと思いました。browser-sync の proxy 機能を利用して browser-sync –> Tomcat 連携することで自動リロードできないか試してみます。
  • その後で Flyway によるテーブル作成処理を実装します。

履歴

2017/08/11
初版発行。
2017/08/13
* DB のマイグレーションツールを Liquibase → Flyway に変更しました。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その12 )( HTML を Thymeleaf テンプレートファイルにする + Controller クラスを作成する )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その11 )( PostCSS で common.css を minify する + autoprefixer, stylelint を導入する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 作成した HTML ファイルを Thymeleaf テンプレートファイルにします。
    • Controller クラスを作成し、画面遷移に必要な js ファイルも実装して、画面の表示と単純な画面遷移ができるようにします。
    • 2回に分けます。今回は入力画面1~3まで実装します。

参照したサイト・書籍

  1. HTML5 & CSS3 デザインレシピ集

    HTML5 & CSS3 デザインレシピ集

    HTML5 & CSS3 デザインレシピ集

  2. HTML5 Form Validation のカスタマイズ
    http://www.girliemac.com/blog/2012/12/01/html5-form-validation-jp/

  3. WAI-ARIAを活用したフロントエンド実装 - 第1回 role属性、aria属性の基礎知識
    https://app.codegrid.net/entry/wai-aria-1

  4. Spring Bootでリダイレクト先のURLを組み立てる
    http://qiita.com/rubytomato@github/items/8d132dec042f695e50f6

  5. Guide to UriComponentsBuilder in Spring
    http://www.baeldung.com/spring-uricomponentsbuilder

  6. jQueryイベントハンドラでreturn falseするとイベントのバブリングが止まる
    http://webtech-walker.com/archive/2012/09/event_handler_return_false.html

目次

  1. HTML ファイルを少し修正する
  2. Thymeleaf を 3.0.6 → 3.0.7 へバージョンアップする
  3. HTML ファイルから Thymeleaf テンプレートファイルを作成する
  4. ksbysample-webapp-lending から config ディレクトリをコピーする
  5. URL を定義する定数クラスを作成する
  6. Controller クラスを作成する
  7. 画面遷移用の js ファイルを実装する
  8. 動作確認
  9. メモ書き

手順

HTML ファイルを少し修正する

最近画面を作っているからか、ふと書店で HTML5 & CSS3 デザインレシピ集 という本を見かけて気になって読んでみて、また家に帰ってから Web でいろいろ調べて HTML5 Form Validation のカスタマイズ という記事を読んで気づきましたが、入力エラー時に入力項目に吹き出しが出る画面を見かけることがありますがあれば HTML5 + CSS3 の機能だったのかとか、HTML5 + CSS3 だけでも結構入力チェックできるんだなとか、自分には HTML5 + CSS3 に関する知識が結構無いんだな。。。とか。

また試しに required 属性を付けて試してみようとしたところ aria-required という属性が候補に表示されました。

f:id:ksby:20170806120707p:plain

aria- って何?と思い調べると WAI-ARIAを活用したフロントエンド実装 - 第1回 role属性、aria属性の基礎知識 の記事が見つかりました。そんな仕様が出来ているとは。。。 全然知りませんでした。

知らないことが多いなあと思いつつ、HTML5 & CSS3 デザインレシピ集 の本は購入したのでさっと読んでどんなことが出来るのかは理解しておこうと思います。

今回の画面では入力チェックは全て Javascript で実装して HTML5 + CSS3 では行いませんが、作成した HTML で autofocus 属性を付けておくのを忘れていたので、一番最初の項目に追加します。

例えば static/inquiry/input01.html の場合には、placeholder="例)田中" の後に autofocus を追加します。

            <!-- お名前(漢字) -->
            <div class="form-group" id="form-group-name">
              <div class="control-label col-sm-2">
                <label class="float-label">お名前(漢字)</label>
                <div class="label label-required">必須</div>
              </div>
              <div class="col-sm-10">
                <div class="row"><div class="col-sm-10">
                  <input type="text" name="lastname" id="lastname" class="form-control form-control-inline" style="width: 150px;" value="" placeholder="例)田中" autofocus/>
                  <input type="text" name="firstname" id="firstname" class="form-control form-control-inline" style="width: 150px;" value="" placeholder="例)太郎"/>
                </div></div>
                <div class="row hidden js-errmsg"><div class="col-sm-10"><p class="form-control-static text-danger"><small>ここにエラーメッセージを表示します</small></p></div></div>
              </div>
            </div>

画面を表示すると、以下の画像のように autofocus 属性を付けた入力項目にフォーカスがセットされます。

f:id:ksby:20170806114945p:plain

Thymeleaf を 3.0.6 → 3.0.7 へバージョンアップする

http://www.thymeleaf.org/ を見たところ、いつの間にか 3.0.7 がリリースされていました。build.gradle には 3.0.6 を記述していたので 3.0.7 へバージョンアップします。

build.gradle の dependencyManagement 内の記述を以下のように変更します。

dependencyManagement {
    imports {
        // BOM は https://repo.spring.io/release/io/spring/platform/platform-bom/Brussels-SR3/
        // の下を見ること
        mavenBom("io.spring.platform:platform-bom:Brussels-SR3") {
            bomProperty 'guava.version', '22.0'
            bomProperty 'thymeleaf.version', '3.0.7.RELEASE'
            bomProperty 'thymeleaf-extras-springsecurity4.version', '3.0.2.RELEASE'
            bomProperty 'thymeleaf-layout-dialect.version', '2.2.2'
            bomProperty 'thymeleaf-extras-data-attribute.version', '2.0.1'
            bomProperty 'thymeleaf-extras-java8time.version', '3.0.0.RELEASE'
        }
    }
}
  • bomProperty 'thymeleaf.version', '3.0.6.RELEASE'bomProperty 'thymeleaf.version', '3.0.7.RELEASE' に変更します。

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

HTML ファイルから Thymeleaf テンプレートファイルを作成する

src/main/resources/templates の下に web/common, web/inquiry の2つのディレクトリを新規作成します。

まずは src/main/resources/templates/web/common の下に共通部分定義用の fragments.html を作成します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="common_header(title, links, style)">
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title th:replace="${title} ?: _">画面名</title>
  <!-- Tell the browser to be responsive to screen width -->
  <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
  <!-- Bootstrap 3.3.6 -->
  <link rel="stylesheet" href="/vendor/bootstrap/css/bootstrap.min.css">
  <!-- Font Awesome -->
  <link rel="stylesheet" href="/vendor/font-awesome/css/font-awesome.min.css">
  <!-- Ionicons -->
  <link rel="stylesheet" href="/vendor/ionicons/css/ionicons.min.css">
  <!-- Theme style -->
  <link rel="stylesheet" href="/vendor/admin-lte/css/AdminLTE.min.css">
  <link rel="stylesheet" href="/vendor/admin-lte/css/skins/skin-blue.min.css">

  <!-- ここに各htmlで定義された link タグが追加される -->
  <th:block th:replace="${links} ?: _"/>

  <!-- 画面共通で使用する css -->
  <link rel="stylesheet" href="/css/common.min.css">

  <!-- ここに各htmlで定義された style タグが追加される -->
  <th:block th:replace="${style} ?: _"/>
</head>

<body class="skin-blue layout-top-nav">
<div class="wrapper">
</div>
</body>
</html>
  • このファイルも Thymeleaf テンプレートファイルなので、<html> ではなく <html xmlns:th="http://www.thymeleaf.org"> を記述します。
  • <head> タグには th:fragment="common_header(title, links, style)" を追加します。
  • <title> タグには th:replace="${title} ?: _" を追加します。
  • <!-- ここに各htmlで定義された link タグが追加される --> <th:block th:replace="${links} ?: _"/> を追加します。
  • <!-- ここに各htmlで定義された style タグが追加される --> <th:block th:replace="${style} ?: _"/> を追加します。

次に、src/main/resources/templates/web/inquiry の下に static/inquiry の下の全てのファイルをコピーした後、以下の点を変更します。

  • <html><html xmlns:th="http://www.thymeleaf.org"> に変更します。
  • <head><head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}"> に変更します。
  • <head> ... </head> 内は以下の記述のみ残します。
    • <title> ... </title>
    • 独自に定義した <link ...>
    • 独自に定義した <style> ... </style>
  • complete.html 以外は <form> タグの最後に th:action="@{/inquiry/input/01/}" を追加します(@{...} の中は自分の画面の URL にします)。これにより CSRF対策用のトークンが自動で挿入されます。

各ファイルの body タグより上の記述は以下のようになります。

■src/main/resources/templates/web/inquiry/input01.html

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 入力画面1</title>
</head>

■src/main/resources/templates/web/inquiry/input02.html

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 入力画面2</title>
</head>

■src/main/resources/templates/web/inquiry/input03.html

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 入力画面3</title>

  <style>
    /* 「チェックボックス複数行」の入力項目のチェックボックスを複数行に書くので、 */
    /* 異なる行のチェックボックスの位置を左揃えにする                         */
    @media (min-width: 768px) {
      #multiline-checkbox .checkbox label {
        display: block;
        float: left;
        width: 180px;
      }
    }
    @media (max-width: 767px) {
      #multiline-checkbox .checkbox label {
        display: block;
        float: left;
        width: 100%;
      }
    }
  </style>
</head>

■src/main/resources/templates/web/inquiry/confirm.html

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 確認画面</title>

  <style>
    /* セルの上部の罫線を表示しないようようにし、セル内の余白を詰める */
    .table > tbody> tr > th,
    .table > tbody> tr > td {
      border-top: none;
      padding: 5px;
    }
  </style>
</head>

■src/main/resources/templates/web/inquiry/complete.html

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head th:replace="~{web/common/fragments :: common_header(~{::title}, ~{::link}, ~{::style})}">
  <title>入力フォーム - 完了画面</title>
</head>

ksbysample-webapp-lending から config ディレクトリをコピーする

Project 作成時に config ディレクトリの下の checkstyle, findbugs, pmd 用の設定ファイルをコピーするのを忘れていました。ksbysample-webapp-lending から以下のファイルを config ディレクトリごとコピーします。

また IntelliJ IDEA のメインメニューから「File」-「Settings」を選択して「Settings」ダイアログを表示した後、CheckstyleFindBugs-IDEA の設定を変更します。

画面左側の一覧から「Other Settings」-「Checkstyle」を選択した後、画面右側の「Configuration File」の右にある「+」ボタンをクリックします。

f:id:ksby:20170806193223p:plain

ダイアログが表示されますので、以下の画像のように入力した後「Next」ボタンをクリックします。

f:id:ksby:20170806193337p:plain

  • 「Description」に google_checks.xml を入力します。
  • 「Use a local Checkstyle file」の「File」に C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\config\checkstyle\google_checks.xml を入力し、「Store relative to project location」をチェックします。

“The rules file has been validated and is ready to add.” のメッセージの画面が表示されたら「Finish」ボタンをクリックします。

「Settings」ダイアログに戻ると「Configuration File」に “google_checks.xml” が追加されていますので、左側の「Active」のチェックボックスをチェックした後「Apply」ボタンをクリックします。

f:id:ksby:20170806193723p:plain

画面左側の一覧から「Other Settings」-「FindBugs-IDEA」を選択した後、画面右側の「Filter」タブをクリックしてから画面中央の「Exclude filter files」の「+」ボタンをクリックします。

f:id:ksby:20170807002949p:plain

「Exclude Filter Files」ダイアログが表示されますので、C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\config\findbugs\findbugs-exclude.xml を選択して「OK」ボタンをクリックします。

f:id:ksby:20170807003204p:plain

「Settings」ダイアログに戻ると「Exclude filter files」に C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample\config\findbugs\findbugs-exclude.xml が表示されています。「OK」ボタンをクリックしてダイアログを閉じます。

f:id:ksby:20170807003338p:plain

URL を定義する定数クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb の下に constants パッケージを新規作成します。

src/main/java/ksbysample/webapp/bootnpmgeb/constants の下に UrlConst.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.constants;

/**
 * URL 定数定義用クラス
 */
public class UrlConst {

    public static final String URL_INQUIRY_BASE = "/inquiry";
    public static final String URL_INQUIRY_INPUT_01 = URL_INQUIRY_BASE + "/input/01/";
    public static final String URL_INQUIRY_INPUT_02 = URL_INQUIRY_BASE + "/input/02/";
    public static final String URL_INQUIRY_INPUT_03 = URL_INQUIRY_BASE + "/input/03/";
    public static final String URL_INQUIRY_CONFIRM = URL_INQUIRY_BASE + "/confirm/";
    public static final String URL_INQUIRY_COMPLETE = URL_INQUIRY_BASE + "/complete/";

}

Controller クラスを作成する

src/main/java/ksbysample/webapp/bootnpmgeb の下に web.inquiry パッケージを作成します。

src/main/java/ksbysample/webapp/bootnpmgeb/web/inquiry の下に InquiryInputController.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.web.inquiry;

import ksbysample.webapp.bootnpmgeb.constants.UrlConst;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * 入力画面1~3用 Controller クラス
 */
@Controller
@RequestMapping("/inquiry/input")
public class InquiryInputController {

    private static final String TEMPLATE_BASE = "web/inquiry";
    private static final String TEMPLATE_INPUT01 = TEMPLATE_BASE + "/input01";
    private static final String TEMPLATE_INPUT02 = TEMPLATE_BASE + "/input02";
    private static final String TEMPLATE_INPUT03 = TEMPLATE_BASE + "/input03";

    /**
     * 入力画面1 初期表示処理
     *
     * @return 入力画面1の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping("/01")
    public String input01() {
        return TEMPLATE_INPUT01;
    }

    /**
     * 入力画面1 「次へ」ボタンクリック時の処理
     *
     * @return 入力画面2の URL
     */
    @PostMapping(value = "/01", params = {"move=next"})
    public String input01MoveNext(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_INPUT_02).toUriString();
    }

    /**
     * 入力画面2 初期表示処理
     *
     * @return 入力画面2の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping("/02")
    public String input02() {
        return TEMPLATE_INPUT02;
    }

    /**
     * 入力画面2 「前へ」ボタンクリック時の処理
     *
     * @return 入力画面1の URL
     */
    @PostMapping(value = "/02", params = {"move=back"})
    public String input02MoveBack(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_INPUT_01).toUriString();
    }

    /**
     * 入力画面2 「次へ」ボタンクリック時の処理
     *
     * @return 入力画面3の URL
     */
    @PostMapping(value = "/02", params = {"move=next"})
    public String input02MoveNext(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_INPUT_03).toUriString();
    }

    /**
     * 入力画面3 初期表示処理
     *
     * @return 入力画面3の Thymeleaf テンプレートファイルのパス
     */
    @GetMapping("/03")
    public String input03() {
        return TEMPLATE_INPUT03;
    }

    /**
     * 入力画面3 「前へ」ボタンクリック時の処理
     *
     * @return 入力画面2の URL
     */
    @PostMapping(value = "/03", params = {"move=back"})
    public String input03MoveBack(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_INPUT_02).toUriString();
    }

    /**
     * 入力画面3 「確認画面へ」ボタンクリック時の処理
     *
     * @return 確認画面の URL
     */
    @PostMapping(value = "/03", params = {"move=next"})
    public String input03MoveNext(UriComponentsBuilder builder) {
        return UrlBasedViewResolver.REDIRECT_URL_PREFIX
                + builder.path(UrlConst.URL_INQUIRY_CONFIRM).toUriString();
    }

}

画面遷移用の js ファイルを実装する

webpack.config.js を以下のように変更します。

module.exports = {
    entry: {
        "js/app": ["./src/main/assets/js/app.js"],
        "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"],
        "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"],
        "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"]
    },
    output: {
        path: __dirname + "/src/main/resources/static",
        publicPath: "/",
        filename: "[name].js"
    }
};
  • 以下の3行を追加します。
    • "js/inquiry/input01": ["./src/main/assets/js/inquiry/input01.js"]
    • "js/inquiry/input02": ["./src/main/assets/js/inquiry/input02.js"]
    • "js/inquiry/input03": ["./src/main/assets/js/inquiry/input03.js"]

js ファイル実装前に npm run server コマンドを実行しておきます。

src/main/assets/js/inquiry の下に input01.js を新規作成し、以下の内容を記述します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    // 動作確認のために初期表示時に「次へ」ボタンをクリック可能にする
    $(".js-btn-next").prop("disabled", false);

    $(".js-btn-next").on("click", function (event) {
        $("#input01Form").attr("action", "/inquiry/input/01/?move=next");
        $("#input01Form").submit();

        // return false は
        // event.preventDefault() + event.stopPropagation() らしい
        return false;
    })
});

src/main/assets/js/inquiry の下に input02.js を新規作成し、以下の内容を記述します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    // 動作確認のために初期表示時に「次へ」ボタンをクリック可能にする
    $(".js-btn-next").prop("disabled", false);

    $(".js-btn-back").on("click", function (event) {
        $("#input02Form").attr("action", "/inquiry/input/02/?move=back");
        $("#input02Form").submit();
        return false;
    })

    $(".js-btn-next").on("click", function (event) {
        $("#input02Form").attr("action", "/inquiry/input/02/?move=next");
        $("#input02Form").submit();
        return false;
    })
});

src/main/assets/js/inquiry の下に input03.js を新規作成し、以下の内容を記述します。

var $ = require("admin-lte/plugins/jQuery/jquery-2.2.3.min.js");

$(document).ready(function () {
    // 動作確認のために初期表示時に「確認画面へ」ボタンをクリック可能にする
    $(".js-btn-confirm").prop("disabled", false);

    $(".js-btn-back").on("click", function (event) {
        $("#input03Form").attr("action", "/inquiry/input/03/?move=back");
        $("#input03Form").submit();
        return false;
    })

    $(".js-btn-confirm").on("click", function (event) {
        $("#input03Form").attr("action", "/inquiry/input/03/?move=next");
        $("#input03Form").submit();
        return false;
    })
});

動作確認

Tomcat を起動します。

ブラウザを起動し http://localhost:8080/inquiry/input/01/ にアクセスすると入力画面1が表示されて、「次へ」ボタンもクリックできるようになっています。

f:id:ksby:20170811004724p:plain

「次へ」ボタンをクリックすると入力画面2へ遷移します。アドレスバーに表示される URL は http://localhost:8080/inquiry/input/02/ に変わっています。

f:id:ksby:20170811004855p:plain

「次へ」ボタンをクリックすると入力画面3へ遷移します。アドレスバーに表示される URL は http://localhost:8080/inquiry/input/03/ に変わっています。

f:id:ksby:20170811005023p:plain

「前の画面へ戻る」ボタンをクリックすると入力画面3→入力画面2→入力画面1へ戻り、URL もきちんと変わりました。

メモ書き

  • 現在の設定では、js ファイルを修正すると webpack の watch 機能で検知されて自動でバンドルされた js ファイルが出力されますが、IntelliJ IDEA で Ctrl+F9 を押して build しないとブラウザに反映されませんでした。browser-sync で html を作成していた時と比較してかなり手間がかかるようになってしまいました。もう少し開発しやすくできないものでしょうか。。。

履歴

2017/08/11
初版発行。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( 番外編 )( IntelliJ IDEA 2017.2 の新機能 Run Dashboard を試してみる )

記事一覧はこちらです。

IntelliJ IDEA 2017.2.1 で Run Dashboard が正式に新機能として公開されましたので、試してみます。2017.2 では -Dide.run.dashboard=true オプションを指定する必要がありましたが、2017.2.1 からは不要になりました。

目次

  1. IntelliJ IDEA でオープンしている Project の Web アプリケーションを Run Dashboard で実行する
  2. IntelliJ IDEA でオープンしている Project とは別の Project の Web アプリケーションを Run Dashboard で実行する
  3. jar ファイルで提供されている Zipkin Server を Run Dashboard で実行する
  4. IntelliJ IDEA でオープンしている Project とは別の Project を変更したい場合には?
  5. Console を広く表示させたい場合には?
  6. 最後に

本編

IntelliJ IDEA でオープンしている Project の Web アプリケーションを Run Dashboard で実行する

Run ボタンで起動すると、

f:id:ksby:20170805165839p:plain

Run Dashboard ではなく通常の Run Tool Window で起動します。

f:id:ksby:20170805165955p:plain

これを Run Dashboard で起動するには、IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations…」を選択して「Run/Debug Configurations」ダイアログを表示した後、画面左側で「Spring Boot」-「Application」(ここに表示される名称は作成している Web アプリケーションにより異なります)を選択してから、画面右側の中央あたりにある「Show in Run Dashboard」をチェックします。

f:id:ksby:20170805170710p:plain

「OK」ボタンをクリックして「Run/Debug Configurations」ダイアログを閉じてから、再度 Run ボタンで起動します。

今度は Run Dashboard で Web アプリケーションが起動します。

f:id:ksby:20170805170940p:plain

IntelliJ IDEA でオープンしている Project とは別の Project の Web アプリケーションを Run Dashboard で実行する

今度は IDEA で boot-npm-geb-sample プロジェクトをオープンしたまま、ksbysample-webapp-lending プロジェクトの Web アプリケーションを実行してみます。

まずは ksbysample-webapp-lending プロジェクトを開いてから build タスクを実行して build\libs の下に jar ファイル(今回は ksbysample-webapp-lending-1.5.4-RELEASE.jar)を作成しておきます。

次に IDEA で boot-npm-geb-sample プロジェクトを開いた後、メインメニューから「Run」-「Edit Configurations…」を選択して「Run/Debug Configurations」ダイアログを表示します。

ダイアログ左上の「+」ボタンをクリックして「Add New Configuration」のリストを表示した後、「Spring Boot」を選択します。

f:id:ksby:20170805172226p:plain

画面左側の「Spring Boot」の下に「Unnamed」が追加されますので、「Unnamed」の設定を以下の画像のように変更します。

f:id:ksby:20170805173501p:plain

  • 「Name」を Unnamedksbysample-webapp-lending に変更します。
  • 「Main class」に ksbysample-webapp-lending のメインクラスである ksbysample.webapp.lending.Application を入力します。
  • VM options」に -Dspring.profiles.active=develop -Dfile.encoding=UTF-8 -jar ksbysample-webapp-lending-1.5.4-RELEASE.jar を入力します。必ず -D... の設定は -jar の前に入れます(前に入れないと無視されます)。
  • 「Working directory」に jar ファイルがある C:\project-springboot\ksbysample-webapp-lending\build\libs を入力します。
  • 「Use classpath of module」で boot-npm-geb-sample_main を選択します。ここは何かを選択しておかないと Run Dashboard で起動できません(実際のクラスパスは -jar オプションを指定しているので jar ファイル内の方になります)。
  • JRE」に起動に使用する JRE を指定します。Default のままでも構いませんが、今回は 1.8.0_141 を選択しておきます。
  • 「Show in Run Dashboard」をチェックします。
  • 尚、-Dspring.profiles.active=develop ではなく「Environment variables」で SPRING_PROFILES_ACTIVE=develop としても反映されませんでした。「Show in Run Dashboard」の下にある「Active Profiles」に develop と入力しても反映されませんでした。

「OK」ボタンをクリックして「Run/Debug Configurations」ダイアログを閉じます。

Run Dashboard を表示すると「Stopped」の下に「ksbysample-webapp-lending」が追加されており、選択すると画面左側の「Run」ボタンがクリックできるようになっています(設定が足りなかったり間違っていると「Run」ボタンは色なしで表示されクリックできません)。

f:id:ksby:20170805173750p:plain

「Run」ボタンをクリックすると ksbysample-webapp-lending Web アプリケーションが起動します。もし他の Web アプリケーションと使用するポート番号が重複する場合には「Run/Debug Configurations」ダイアログの「VM options」で -Dserver.port=... オプションを追加してポート番号を変更します。

f:id:ksby:20170805174307p:plain

jar ファイルで提供されている Zipkin Server を Run Dashboard で実行する

今度は Zipkin Server を Run Dashboard から起動してみます。Zipkin Server の jar ファイルは C:\zipkin の下に入れてある前提で記載します。

f:id:ksby:20170805175804p:plain

やることは ksbysample-webapp-lending プロジェクトの時と同じですので画像は必要なものだけ掲載します。

最初に Zipkin server のメインクラスを確認します。コマンドラインから java -jar zipkin-server-1.21.0-exec.jar を実行するとメインクラス zipkin.server.ZipkinServer が表示されます。

f:id:ksby:20170805180423p:plain

IntelliJ IDEA のメインメニューから「Run」-「Edit Configurations…」を選択して「Run/Debug Configurations」ダイアログを表示します。

ダイアログ左上の「+」ボタンをクリックして「Add New Configuration」のリストを表示した後、「Spring Boot」を選択します。

画面左側の「Spring Boot」の下に「Unnamed」が追加されますので、「Unnamed」の設定を以下の画像のように変更します。

f:id:ksby:20170805180739p:plain

  • 「Name」を UnnamedZipkin server に変更します。
  • 「Main class」に zipkin.server.ZipkinServer を入力します。
  • VM options」に -jar zipkin-server-1.21.0-exec.jar を入力します。
  • 「Working directory」に jar ファイルがある C:\zipkin を入力します。
  • 「Use classpath of module」で boot-npm-geb-sample_main を選択します。
  • JRE」に起動に使用する JRE を指定します。Default のままでも構いませんが、今回は 1.8.0_141 を選択しておきます。
  • 「Show in Run Dashboard」をチェックします。

「OK」ボタンをクリックして「Run/Debug Configurations」ダイアログを閉じます。

Run Dashboard を表示すると「Stopped」の下に「Zipkin server」が追加されており、選択すると画面左側の「Run」ボタンがクリックできるようになっています。

f:id:ksby:20170805181047p:plain

「Run」ボタンをクリックすると Zipkin server が起動します。

f:id:ksby:20170805181227p:plain

http://localhost:9411/ にアクセスすると Zipkin の画面が表示されました。

f:id:ksby:20170805181344p:plain

IntelliJ IDEA でオープンしている Project とは別の Project を修正したくなった場合には?

IntelliJ IDEA のメインメニューから「File」-「Open Recent」を選択すると最近オープンした Project のリストが表示されますので、そこから変更したい Project を選択します。

f:id:ksby:20170805204820p:plain

「Open Project」ダイアログが表示されますので、「New Window」ボタンをクリックします。

f:id:ksby:20170805210125p:plain

別の IntelliJ IDEA が起動してそちらで Project が開きますので、そこで変更して jar ファイルを作り直します。

Console を広く表示させたい場合には?

自分が使用しているノートPCだと横幅が狭いので、Console を通常の Run Tool Window で起動しているのと同じように横いっぱいに表示させたいなと思ったのですが、その場合には「Show Configurations」のボタンをクリックすると、

f:id:ksby:20170806011652p:plain

左側の表示が消えて Console が広く表示されます。

f:id:ksby:20170806011853p:plain

また画面上部の実行中のアプリケーションが表示されている部分をクリックすることで、Console を表示しているアプリケーションを切り替えることができます。

f:id:ksby:20170806012034p:plain

最後に

  • 2017.2 のイチオシの機能だと思っていたのですが、設定方法を説明しているページが見つかりません。。。 上の内容は何となく試した結果なのですが、ここはこうする方がいいよという点があるかもしれません。
  • 複数の Web アプリケーションを1画面上から起動・停止したり、Console や Spring Boot Actuator の内容を見れるのは便利ですね。

IntelliJ IDEA を 2017.2 → 2017.2.1 へバージョンアップ

IntelliJ IDEA を 2017.2 → 2017.2.1 へバージョンアップする

IntelliJ IDEA の 2017.2.1 がリリースされたのでバージョンアップします。

※ksbysample-webapp-lending プロジェクトを開いた状態でバージョンアップしています。

  1. IntelliJ IDEA のメインメニューから「Help」-「Check for Updates…」を選択します。

  2. IDE and Plugin Updates」ダイアログが表示されます。左下に「Update and Restart」ボタンが表示されていますので、「Update and Restart」ボタンをクリックします。

    f:id:ksby:20170805092559p:plain

  3. Plugin の update も表示されました。「Error-prone Compiler Integration」はバージョンアップすると動かなくなりますので、これだけチェックを外して「Update and Restart」ボタンをクリックします。

    f:id:ksby:20170805092755p:plain

  4. Patch がダウンロードされて IntelliJ IDEA が再起動します。

  5. IntelliJ IDEA が起動すると画面下部に「Indexing…」のメッセージが表示されますので、終了するまで待機します。

    f:id:ksby:20170805095416p:plain

  6. Gradle Tool Window のツリーを見ると「Tasks」の下に「other」しかない状態になっているので、左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

    f:id:ksby:20170805095637p:plain

  7. IntelliJ IDEA のメインメニューから「Help」-「About」を選択し、2017.2.1 へバージョンアップされていることを確認します。

  8. clean タスク実行 → Rebuild Project 実行 → build タスクを実行して、"BUILD SUCCESSFUL" のメッセージが出力されることを確認します。

    f:id:ksby:20170805100254p:plain

  9. Project Tool Window で src/test を選択した後、コンテキストメニューを表示して「Run ‘All Tests’ with Coverage」を選択し、テストが全て成功することを確認します。

    f:id:ksby:20170805100752p:plain