IntelliJ で Spring Boot を動作させる場合にホットデプロイの設定でハマる事例が後を絶たないらしく、情報を調べていても Spring Boot のバージョンもまちまちでなかなかこれといった解決策に辿り着かない。 休日に試行錯誤していたがとても苦戦したので備忘。 尚ビルドシステムとしては Gradle を使用することとする。

ちなみに私は Spring Boot は初めてなので理解が怪しいところがあるかもしれない。

新規 Spring Boot プロジェクト

Spring は 10 年以上前から存在する歴史ある Java のフレームワークだが、昔は Spring ですべて賄うというよりは Struts と Spring を組み合わせて構築するといったパターンが多かったように思う。 その後途中あたりから Spring MVC という Web アプリケーションを作成する為のパッケージが出てきていわゆる「XML 地獄」から開放され、その後に更に Spring の各パッケージを適切に組み合わせて簡単に使えるようにしたオールインワンパッケージのようなものが Spring Boot ……だと思う。

IntelliJ IDEA Ultimate で新規 Spring Boot プロジェクトを作成する時は「新規プロジェクトを作成する」から Spring Initializr を選ぶ。 「Spring」の方ではない。 こちらを選択すると Spring Boot ではなく通常の Spring になってしまうと思われる。 また「Initializr」は typo ではなく公式にこのスペルで書かれている。

まず最初の知として現時点では Java 9 は避けるのが望ましい。 どうも Gradle や Spring のバージョンによって Java 9 だと正しく動かないらしく原因不明のエラーに悩まされた。 どうしても使いたいと言うのでない限り Java 8 にしておくのが無難だ。 モダンなコードを書きたいのであれば Kotlin を採用する方法もある。 私は未検証だが Web 上に使用例が幾つかあるし IDE 上でも Kotlin の選択肢が示されている。

依存関係を選ぶ箇所ではとりあえず最小構成として「Web」と「Thymeleaf」を選択しておく。 Thymeleaf (タイムリーフ) は Spring で推奨されているテンプレートエンジンらしい。 JSP を書かされるよりは遥かにいい。

ホットデプロイのハマり箇所

さて、ホットデプロイである。 これが出来ないとソースコードを書き換える度に組み込みの Tomcat を再起動しなければ変更が反映されず非効率な開発を強いられることになる。 下記に自分がハマったことを列挙する。

Spring Loaded でなく Spring Boot DevTools を使う

「Spring Boot ホットデプロイ」で情報を探していると「Spring Loaded」という単語がヒットする。 これは Spring Boot v1.2 以前の 2 年前まで有効だった情報だ。 今は Spring Boot DevTools の方を使用する。 導入は build.gradle に以下を追加すればよい:

dependencies {
    compile('org.springframework.boot:spring-boot-devtools')
}

IntelliJ IDEA 管理下でなければこれだけでも動くのかもしれないが IntelliJ だとまだ足りない。 以下に進む。

自動的にビルドする設定とレジストリ

How to Use Spring Boot Live Reload with IntelliJ を参照 (英語)。 IntelliJ で以下の 2 点を設定する:

  1. Settings -> Build, Execution, Deployment -> Compiler -> Make project automatically にチェックを入れる。これをしていないとソースコードを変更しても自動ビルドされない
  2. SHIFT + CTRL + A で出てくるウィンドウで Registry を検索し compiler.automake.allow.when.app.running にチェックを入れる。尚 CTRL + SHIFT + ALT + / でも Registry を選択できた。

さて、ここまで実行して gradlew.bat bootRun を実行 (もしくは IntelliJ IDEA 上で Gradle タスク実行) してみる。 やはりソースコードもテンプレートも変更が即反映されない。 そこで以下の設定を行う。

IntelliJ 用に自動ビルド時の出力先ディレクトリを調整

デフォルトで Spring Boot DevTools によって /build/classes/main//build/resources/main/ 以下の変更が監視され即反映されるのだが IntelliJ が自動ビルドした時のクラスファイルの保存先がこのディレクトリになっていない。 よって build.gradle に以下の記述を追加する:

apply plugin: 'idea'

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

上記は Gradle のバージョンが 3.x.x の例なのだが Gradle のバージョンを 4.2.1 などに上げるとディレクトリ構造が異なってしまいうまく動かなくなってしまうので注意。 $buildDir/classes/main/ でなく $buildDir/classes/java/main/ などといったように /java サブディレクトリが増えている。 恐らく Kotlin 対応のためだろう。 この先またディレクトリが変更されたとしても /build/classes 以下の構造をよく見て同じように指定してやれば良い。

これでソースコードのホットデプロイはできるようになった。 ホットデプロイに成功した場合はコンソールのログに再デプロイのログが流れるので分かりやすい。

Thymeleaf テンプレートの反映に失敗する場合

特にこれがハマった。 application.propertiesspring.thymeleaf.cache=false を指定するという情報もあるが、そもそも DevTools ではこの設定がデフォルトになっているようで不要のようだ。

結論から言うとコンソールから gradlew.bat bootRun 実行 (または IntelliJ 上からの同様の操作) を行った場合はテンプレートファイルを変更しても全く反映されない。 @SpringBootApplication アノテーションが付与された main() を実行 (つまりクラスファイルを右クリックして実行など) すると同様にビルトインされた Tomcat サーバが起動するが、こちらだと何故かテンプレートファイルが即時反映される。 よく分からないがこういうもののようだ。