ADliveテックブログ

ADlive株式会社の技術ブログです。

Spring BootでDeveloper ToolsとLiveReloadを使ってサクサクWeb開発

はじめに

 

少し前までのJavaでのWeb開発って、

  1. コードを書く
  2. Tomcatを再起動
  3. 数十秒待つ
  4. ブラウザリロードする

こんなイメージありませんか?

再起動の待ち時間の間にうっかりTwitterやはてぶを開いてしまって、何やっているか忘れてしまっていたり・・・。待ちにより思考が分断され、なかなか開発効率があがらない状態になったりします。これが嫌で他の言語などに流れていってしまった方も多いのではないでしょうか。

JRebelやいくつかのフレームワークでは動的なリロード(Hot Reloading)をサポートしていますが、有償だったり設定が面倒だったりで、あまり普及しているとは言い難い状況でした。

Spring BootでDevelper ToolsとLiveReloadを使うと他の言語でのWeb開発と同様、再起動不要でサクサク開発ができるようになります。開発者は「コードを書いて保存する」だけです。Tomcatの再起動はもちろんブラウザのリロードも不要になります。

Spring BootのドキュメントにはDevelper Toolsの設定方法がありますが、LiveReloadの詳しい説明はLiveReloadのサイトを参照する必要があったり、IntelliJ上での設定はWeb上に点在していたりして、きちんと動作させるのには少しだけコツがいります。

このエントリでは、一からSpring Bootのプロジェクトを作成し、Spring BootのDevelper ToolsとLiveReloadを設定して、IntelliJ上で動作させるまでをステップバイステップで説明します。

前準備

JDKIntelliJ IDEAはインストールしておきます。JDKは8で確認していますが、JDK 11でも同様に動作するはずです。またIntelliJ IDEAはCommunity 版の2018.3で動作確認していますが、Ultimate版でも基本的には同じです。

また、サンプルコードはKotlinで書いていますが、こちらもJavaで書いても基本的に同じはずです。適時、Javaで試される方への補足を入れています。また、Kotlin版、Java版の最終のソースコードへのリンクも文末に掲載していますので参考にされてください。

Spring Framework 5からKotlinがサポートされていますし、Web+DB PRESS Vol. 109でKotlin/Spring Bootでの特集が組まれていたり盛り上がってきています。もしKotlin試したことない方がいたら、本エントリでぜひお試しくださいませ。

www.infoq.com

gihyo.jp

Spring Initializrを使ってプロジェクトの雛形を作成

最初にプロジェクト雛形をSpring Initializrのサイト上で作成します。
IntelliJ IDEA Ultimateをお使いの方は、[File] -> [New Project] -> [Spring Initializr]で起動するウィザードからもほぼ同様の操作が可能です。)

  1. https://start.spring.io/をブラウザで表示します。
  2. 以下のように設定を変更します。
    (変更する箇所は太字にしています)

    Project: Gradle Project
    Language: Kotlin
    Spring Boot: 2.1.3
    Project Metadata:
     - Group: com.example
     - Artifact: demo
    Dependencies: 以下の3つを追加
     - Web
     - Thymeleaf
     - DevTools

    以下のような設定になっているはずです。 

    f:id:tech_adlive:20190318165844p:plain

    Spring Initializrの設定


  3. [Generate Project]をクリックして、プロジェクトの雛形ファイルをダウンロードします。
  4. ダウンロードしたzipを適当なディレクトリで解凍します。

IntelliJからプロジェクトを起動する

次にプロジェクトをIntelliJにインポートし、Spring Bootを起動してみましょう。

  1. IntelliJ起動時のダイアログで[Import Project]を選択するか、すでに別のプロジェクトを起動している場合は[File] -> [New] -> [Project from Existing Sources...]を選択し、解凍したディレクトリ直下の[settings.gradle]を選択して、[Open]を実行します。f:id:tech_adlive:20190318171405p:plainf:id:tech_adlive:20190318171608p:plain
  2.  Gradleの設定画面がでるので、そのまま[OK]を押します。

    f:id:tech_adlive:20190318171714p:plain

  3. 右のサイドバーのGradleのパネルから、デバッグ実行で[bootRun]タスクを実行して、Spring bootを起動します。Springのロゴと「Tomcat started on port(s): 8080」というメッセージがコンソールに出れば起動完了です。f:id:tech_adlive:20190319150553p:plain
  4. f:id:tech_adlive:20190318172038p:plain

  5. http://localhost:8080/」にアクセスします。以下の画面が表示されればSpring Bootが起動しています。(まだ画面がないのでエラー画面が表示されています)

    f:id:tech_adlive:20190318172218p:plain

HTMLファイルとコントローラーを追加する

1. 以下の3つのファイルを追加します。

src/main/kotlin/com/example/demo/IndexController.kt

package com.example.demo

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

@Controller
@RequestMapping("")
class IndexController {
  @GetMapping
  fun index(): String = "index"
}

src/main/resouces/templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<body>
  <h1>Hello LiveReload!</h1>
  <script src="/js/index.js"></script>
</body>
</html>

src/main/resouces/static/js/index.js
(jsディレクトリは存在しないので作成してください)

console.log("Welcome LiveReload from js"); 

 2. [Stop] -> [Stop All]を押して、Gradleから起動しているSpring Bootを停止します。

f:id:tech_adlive:20190319150700p:plain

 3. GradleからbootRunを起動します。

 4.「http://localhost:8080/」をリロードします。以下の画面とブラウザのコンソールにログが表示されれば成功です。

f:id:tech_adlive:20190318184518p:plain

f:id:tech_adlive:20190318184500p:plain

Developer Toolsの自動再起動機能を使うための設定(build.gradle)

  1. [build.gradle]に以下の設定を追加して保存します。
    Javaの方は「"$buildDir/classes/kotlin/main"」を「"$buildDir/classes/java/main"」にする必要があります。)

    apply plugin: 'idea'
    
    idea {
        module {
            inheritOutputDirs = false
            outputDir = file("$buildDir/classes/kotlin/main") 
        }
    }
  2. Gradleの設定を読み込むか聞いてくるので[Enable Auto-import]をクリック。
    f:id:tech_adlive:20190318180553p:plain

Developer Toolsの自動再起動機能を使うための設定(IntelliJのプロジェクト設定)

  1. IntelliJで[CMD + Shift + A] (Windowsの場合はCtrl + Shift + A) を実行して、「Build project automatically」と入力し、[Build project automatically]を選択。

    f:id:tech_adlive:20190318175602p:plain

  2. [Build project automatically]にチェックを入れて[OK]をクリック。

    f:id:tech_adlive:20190318175706p:plain

    この設定はプロジェクトごとに保存されます。

Developer Toolsの自動再起動機能を使うための設定(IntelliJのRegistry設定)

  1. IntelliJで[CMD + Shift + A] (Windowsの場合はCtrl + Shift + A) を実行して、「Registry」と入力し、[Registry...]を選択。
    f:id:tech_adlive:20190318175813p:plain

  2. 以下の2つのキーの値を変更して、[Close]をクリック。(アルファベット順にキーが並んでいるので目グレップしてください。)compiler.automake.allow.when.app.running: チェックを入れる
    compiler.automake.postpone.when.idle.less.than: 100 500 (2019-04-18修正)
    f:id:tech_adlive:20190318175950p:plain
    「compiler.automake.allow.when.app.running」はコードを保存すると自動でコンパイルが実行される設定です。チェックを入れることで、コード保存すると自動でコンパイルが実行され、自動再起動が走るようになります。

    「compiler.automake.postpone.when.idle.less.than」はコードを保存してから、コンパイルが実行されるまでの待ち時間の設定です。デフォルトは3秒で少し長いため、100500ミリ秒に変更することで、コンパイルをすぐ実行し、自動再起動が走るようにしています。

    ※「compiler.automake.postpone.when.idle.less.than」の値ですが、プロジェクト内のコードが増えてコンパイル時間が長くなってくると再起動時にコンパイルが間に合わずにエラーが発生する場合があります。プロジェクトのサイズやマシンスペックに合わせて適時変更してください。

    これらの設定はIntelliJ上のグローバルな設定になります。

静的ファイルが反映されるようにする

実は今までの設定では、HTMLテンプレートやJSファイルが自動反映されません。さきほどGradleのbootRunを再起動したのはそのためです。自動反映させるためには、Gradleからではなく@SpringBootApplicationがついているmainメソッドから直接Spring Bootを起動させます。これは1度だけ行えば、その後はGradleから起動しても自動反映されるようになります。(ここについては詳細は不明ですが、おそらくIntelliJ内でのファイルの監視が関係いるのではないかと。理由をご存知の方がいましたらコメントいただけるとありがたいです。)

また、Gradleから起動しているSpring Bootを再起動することにより、IntelliJのプロジェクト設定、Registry設定の反映も兼ねています。

以下の手順でおこないます。

  1. [Stop] を押して、Gradleから起動しているSpring Bootを停止します。

    f:id:tech_adlive:20190319150729p:plain

  2.  [DemoApplication.kt]を右クリックして[Run]を実行します。実行後Spring Bootが起動したら、1ど同じ[Stop]ボタンを停止します。
    f:id:tech_adlive:20190319104406p:plain

  3. 右のサイドバーのGradleのパネルから、[bootRun]タスクを実行して、Spring bootを起動します。(最初のGraldeからの起動操作と同じ)

Developer Toolsによる自動再起動機能を確認する

それでは、自動再起動機能が機能するかどうか確認してみましょう。

  1. 「src/main/kotlin/com/example/demo/DemoApplication.tk」を開いて、printlnで起動時にログを出力するように修正して、保存する。

    f:id:tech_adlive:20190318181335p:plain

    すると自動再起動が走り、コンソールにSpringのロゴが再度表示されログが表示あれるはずです。

    f:id:tech_adlive:20190318181356p:plain

    成功です!出力する文字列を変更するたびに自動再起動が実行されます。

LiveReloadを有効にする

自動再起動の設定は完了しましたが、これだと

  1.  コードを変更する
  2.  再起動を待つ
  3.  ブラウザをリロードする(早すぎるとサーバーが動いていないのでエラー画面になる)

と、まだ3ステップもあり、開発精神安定上よくありません。ここからが本番です。コントローラーとビューを追加して、LiveReloadの設定を追加し、

  1. コードを変更する -> 再起動とブラウザリロードが自動的に走り最新のアプリが画面に表示される。

という1ステップの状態にします。

  1. Chrome Web StoreからLiveReloadのChrome拡張をインストールします。
  2. LiveReloadをロードするscriptタグをコードをHTMLに追加して、ブラウザをリロードします。

src/main/resouces/templates/index.html

  ...
  <script src="/js/index.js"></script>
  <script>document.write('<script src="http://'
  + location.host.split(':')[0]
  + ':35729/livereload.js"></'
  + 'script>')</script>
</body>
</html>

LiveReloadは35729ポートで起動します。コードの変更があるとこのポートを経由して、ブラウザに変更通知を送り、LiveReloadがブラウザをリロードします。


index.jsのコンソールログ、index.htmlの変更、DemoApplication.ktなどKotlinのコード変更、いずれを実行しても画面が自動的に再読込されて、最新のコードが反映された画面が表示されます。

これでTomcatを再起動することも、ブラウザをリロードすることもなく、開発をし続けることができるようになりました!

ちなみにChrome拡張はLiveReloadに必須ではないですが、インストールすることでランタイムエラーなどで画面が表示されない状態のときも、エラーが解消した時点で、自動リロードされたり、より広い範囲で「自動的にリロード」されるようになります。

LiveReloadを本番環境で無効にする

LiveReloadをロードするscriptタグは本番では不要なので、本番環境では出力されないようにします。Developer Tools自体はbuild.gradleでruntimeOnlyになっているので、本番環境では同梱されません。なので「Developer Toolsのクラスが存在するか?」で開発時かどうかを判定し、scriptタグの出力をコントロールします。以下のようにDevToolsUtil.ktを追加し、index.htmlのscriptタグにth:if属性を追加します。これにより、本番環境ではscriptタグが出力されないようになります。
(細かい点ですが、LiveReload Chrome拡張は一度設定するとこのscriptタグがなくても自動で同様のスクリプトを挿入します。scriptが挿入されていないことは、Chrome拡張を切ってから確認してみてください。)

src/main/kotlin/com/example/demo/DevToolsUtil.kt

package com.example.demo

import org.springframework.util.ClassUtils

class DevToolsUtil {
    companion object {
        @JvmStatic fun isDeveloping(): Boolean {
            return ClassUtils.isPresent("org.springframework.boot.devtools.settings.DevToolsSettings",
                    ClassLoader.getSystemClassLoader())
        }
    }
}

src/main/resouces/templates/index.html

...
<script src="/js/index.js"></script>
<script
th:if="${T(com.example.demo.DevToolsUtil).isDeveloping()}">document.write('<script src="http://'
+ location.host.split(':')[0]
+ ':35729/livereload.js"></'
+ 'script>')</script>
</body>
</html>

DevToolsUtilのcompanion objectはJavaで言うところのstaticメソッドの定義、@JavaStaticはJavaからstatic関数を呼び出す際の命名規則Java風にするものです。これにより、ThymeleafのHTMLのEL式で、直感的にstaticメソッドisDevelopingが呼び出せるようになります。詳細は以下の記事を参照ください。

qiita.com

なんか動かないな?って思ったら

筆者の環境では、開発環境やブラウザを立ち上げたまま、Macをスリープさせて次の日にそのまま開くとLiveReloadやHTMLファイルの更新が反映されなくなることがありました。その他、動かない場合、以下を順番に実行すればどれかで解消するはずです。

  • ブラウザのスーパーリロード
  • ブラウザのキャッシュを削除
  • Javaのゾンビプロセスが残っていたら停止する

まとめ

Developer ToolsとLiveReloadを使って、気持ちよく効率的な開発をぜひ体験してみてください。GitHubにもコードは上げていますので、こちらも参考にしてみてください。