Intellij IDEAでSpring Boot3アプリケーションでコードの変更をサーバーの再起動なしに反映(Hotswap)する

cloud storage storage medium 7832676
目次

やりたいこと

JavaおよびThymeleafのソースコードを修正後に、開発用サーバーであるTomcatの再起動を伴わずにソースコードを反映するHotswapを行いたい。本記事ではHotswapの設定方法を紹介します。

以下が環境です。

  • Mac OS Ventura 13.4
  • Intellij IDEA 2023.2
  • Spring boot 3.1.2
  • Java 17

自動再起動(Automatic restart)とホットスワップ(Hotswap)は似て非なる別物

紛らわしい2つのキーワードがあるので整理しておきます。結論としては、開発用のサーバーを再起動せずにソースコードの修正を反映させるためにはJava VMのhotswapを利用する必要があります。これはIntellij IDEAやEclipse等のIDEに備わっている機能を通じて利用できます。そして、これはspring-boot-devtoolsのautomatic restartとは別物です。

自動再起動とホットスワップの違いまとめ

名称 機能の提供元 制限
自動再起動
(Automatic restart)
spring-boot-devtoolsによる機能 Classのシグネチャ変更を変更してもソースコードの更新が反映される。
コンテナ(Tomcat)の再起動を伴うため数秒-数十秒程度の時間がかかる。再起動中はアプリケーションが利用できない。Run/Debug実行両方でサポートされる。
ホットスワップ
(Hotswap)
Java VMによる機能 メソッド本体が変更された場合のみ利用可能です。シグネチャーの変更はサポートされていません。
クラスメンバーの追加と削除はサポートされていません

コンテナ(Tomcat)の再起動を伴わないため変更は即時反映される。再起動を伴わないのでアプリケーションが利用出来ないタイミングはない。Debug実行でのみサポートされ、Run実行ではサポートされない。

プログラムの実行フローを変更する | IntelliJ IDEA ドキュメント

場合によっては、コードを軽微に変更しているときに、プロセスを停止することなく、動作中のアプリケーションでどのように動作するかをすぐに確認したいことがあります。ホットスワップメカニズムを使用すると、アプリケーション全体を再起動しなくても、デバッグセッション中に変更されたクラスを再ロードすることができます。

Spring Boot での開発 – リファレンスドキュメント

spring-boot-devtools を使用するアプリケーションは、クラスパス上のファイルが変更されるたびに自動的に再起動します。

20. Developer tools

20.2 Automatic restart

Applications that use spring-boot-devtools will automatically restart whenever files on the classpath change. This can be a useful feature when working in an IDE as it gives a very fast feedback loop for code changes. By default, any entry on the classpath that points to a folder will be monitored for changes. Note that certain resources such as static assets and view templates do not need to restart the application.

他の記事では、spring-boot-devtoolsによるAutomatic restartのことをHotswapと呼んでいるケースが多いようです。上記の表でも整理したように、spring-boot-devtoolsによるAutomatic restartはHotswapではありません。Automatic restartはコンテナ(Tomcat)の再起動を伴うため、再起動の時間待つ必要があり、生産性が大きく向上するとは言い難いです。

spring-boot-devtoolsとはSpringBootが提供する開発生産性を高めるためのライブラリの一つ

spring-boot-devtoolsはSpringBootが提供するライブラリです。開発生産性を高めるために、上述のautomatic restartやLiveReload等の機能を提供しています。LiveReloadはTomcatの再起動に伴い、ブラウザも更新してくれるというものです。

spring-boot-devtoolsのautomatic restartはTomcatの自動再起動

spring-boot-devtoolsは特定のクラスパスの監視を行います。デフォルトではプロジェクトのクラスファイル配置先のパス。このパス上のファイルが変更された場合。つまりクラスがコンパイルされたタイミングで発動し、Tomcatが再起動します。

Java VMのHotswapはTomcatの自動再起動を伴わない変更反映

JavaのHotswapは元々Java VMに備わっているデバッグ実行時の機能です。コンテナの再起動なくJavaのソースコードの変更を実行中のアプリケーションに反映できます。ただし、制限は上記の表で述べた通りクラスのシグネチャに変更がない場合に限定されます。またデバッグ実行時のみ利用可能です。

Intellij IDEA 等のIDEでは、このJava VMのHotswapを利用してソースコードの変更を即時反映する機能が備わっています。

Hotswapを利用するための設定

Edit Configurationsの設定

Edit Configuration Settingsを開きます。

Modify options > On ‘Update’ actionでコードが更新された際の挙動をHot swap classes and update trigger file if failed.に設定します。

関連するマニュアルも見ておくと、Modify optionsのオプションに関して、Intellij IDEAのマニュアルには以下のようにあります。

  • コードを変更し、実行中のアプリケーションを更新する場合の動作を指定します。
  • 何もしない : アプリケーションを更新しないでください。
  • リソースの更新 : 変更されたすべてのリソースファイルを更新します。
  • クラスとリソースの更新 : 変更されたすべてのリソースファイルを更新し、変更されたすべての Java クラスを再コンパイルします。
  • デバッグ時に、IntelliJ IDEA は更新されたクラスをデプロイして再ロードします。詳細については、修正されたクラスを再ロードするを参照してください。それ以外の場合、アプリケーションを定期的に実行すると、IntelliJ IDEA は出力フォルダー内の変更されたクラスのみを更新します。実行中のアプリケーションにそのようなクラスをデプロイして再ロードするかどうかは、使用している Java ランタイムの機能によって異なります。
  • トリガーファイルの更新 : コマンドラインで -Dspring.devtools.restart.trigger-file=.restartTriggerFile を渡し、.restartTriggerFile ファイルを更新します。これにより、再起動チェックが開始されます。アプリケーションは、何かする必要がある場合にのみ再起動します。
  • クラスのホットスワップと失敗時のトリガーファイル更新 : これは、クラスとリソースの更新の後にトリガーファイルの更新が続くのと似ています。

Spring Boot 実行構成 | IntelliJ IDEA ドキュメント

日本語マニュアルにある以下の説明書きは似ているとだけ書かれていてどのような挙動かはっきりと分かりません。

クラスのホットスワップと失敗時のトリガーファイル更新 : これは、クラスとリソースの更新の後にトリガーファイルの更新が続くのと似ています。

オプションHot swap classes and update trigger file if failed. にマウスホバーした際に以下のヒントが表示されます。

  • デバッグモード
    • すべての修正されたファイルと依存ファイルをコンパイル、クラスをホットスワップする
    • ホットスワップに失敗した場合はトリガーファイルを更新する
  • その他のモード
    • すべての修正されたファイルと依存ファイルをコンパイル
    • トリガーファイルを更新する
In debug mode:
compiles all modified and dependent files, hot swap classes, updates trigger file if hot swap failed.
Otherwise:
compiles all modified and dependent files and update trigger file.
Specifies trigger file name by adding VM option -Dspring.devtools.restart.trigger-file=.restartTriggerFile

クラスをホットスワップするのはDebug modeのみであることが分かります。また、ホットスワップに失敗した場合のトリガーファイルの更新はですが、これは以下のようなものです。

spring-boot-devtoolsはクラスパス以外にもトリガーファイルを監視しており、トリガーファイルが更新された場合に再起動させる。IDEを通じてトリガーファイルを更新することで、spring-boot-devtoolsに明示的にTomcatの再起動を指示する。

20.2.4 Using a trigger file

If you work with an IDE that continuously compiles changed files, you might prefer to trigger restarts only at specific times. To do this you can use a “trigger file”, which is a special file that must be modified when you want to actually trigger a restart check. Changing the file only triggers the check and the restart will only occur if Devtools has detected it has to do something. The trigger file could be updated manually, or via an IDE plugin.

To use a trigger file use the spring.devtools.restart.trigger-file property.

以上でIntellij IDEAでHotswapが利用可能です。

spring-boot-devtoolsの有効化

spring-boot-devtoolsをdependenciesに追加します。

build.gradleに以下のように追加します。

dependencies {
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
}

application.propertiesでautomatic restartを明示的に有効にする場合は以下を追記します。

spring.devtools.restart.enabled=true

spring-boot-devtoolsはhotswapとは関係ないのに何故有効にするのか?という疑問が生じると思います。それは次のような理由によるものです。

Java VMのhotswapをIntellij IDEAから利用するためにRun Debug ConfigurationsHot swap classes and update trigger file if failed.を設定します。「もしhotswapが失敗した場合にはtrigger fileを更新する。」とあります。このtrigger filespring-boot-devtoolsが監視しているファイルで、このトリガーファイルを更新することでspring-boot-devtoolsに更新をさせる事ができます。

spring-boot-devtoolsが無効の状態だと、以下のよううにRun/Debug Configurationsで警告メッセージが表示されます。

Hotswapを試してみる

それでは実際にSpring Bootアプリケーションを作成してHotswapを試してみましょう。

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String sayHello() {
        return "Hello, world";
    }
}

デバッグ実行します。Run実行ではHotswapは動作しません。

ブラウザからhelloメソッドにアクセスします。

ソースファイルを更新します。

return "Hello, there";

以下のいずれかを選択します。

  • Run > Debugging Actions > Update ‘xxx’ application ⌘F10
  • Build > Build Project ⌘F9
  • Build > Recompile 'xxxxxxxx.java' ⇧⌘F9

いまいちUpdateとBuildの違いが良く分かりません。Run Debug ConfigurationsではOn Update Actionとして、Hotswapを選択しましたが、ここでのBuildはUpdateも行われるのでしょうか。

spring-boot-toolsを有効にした状態ではRun > Debugging Actions > Update ‘xxx’ application ⌘F10のコマンドではhotswapが実行されませんでした。

デバッグツールウィンドウに、以下のようにクラスがReloadされたと表示されます。

ブラウザをリロードします。

ソースコードの変更が反映されていることが確認できました。

この間、Tomcatは再起動していません。これでHotswapができていることが確認できました。

Thymeleafの反映

Thymeleafは特に設定を行わずともhtmlを更新すると自動的にブラウザリロードで反映されます。

テンプレートの場所を明示的に指定する、キャッシュを明示的にオフにする場合はapplication.propertiesを以下のように設定します。

spring.thymeleaf.prefix=file:src/main/resources/templates/
spring.thymeleaf.cache=false

Intellij IDEAのPreferencesは関係するか?

以下の2つのオプションはHotswapとは無関係のようです。少なくとも両方ともオフでも上記のHotswapは機能します。また両方をオンにしてもHotswapは機能しました。

Build, Execution, Deployment > Compiler > Build project automatically (only works while not running /debugging)

Advanced Settings > Compiler > Allow auto-make to start even if developed application is currently running

これらを両方Onにする かつRun ConfigurationsのModify options > On ‘Update’ actionを何も選択しない状態でDebug実行すると、

ソースコードの更新後に⌘F9をタイプするとビルド後にTomcatが再起動されました。⌘F10では何も起きません。違いが分かりません。。

どうやらこれらのオプションはspring-boot-devtoolsのAutomatic restartの代わりになる可能性があります。

保存と同時にソースコードを反映させたい

Hotswapは実現できましたが、ソースコードを⌘Sで保存→⌘F10あるいは⌘F9等の2アクションが必要になります。インタープリター型のプログラミング言語であれば保存と同時に変更内容が即反映されるわけですからあとひと手間減らしたいところです。

Intellij IDEAのマクロの機能を使えば2つの操作をまとめて一つのマクロとして登録することが可能です。また登録したマクロに対してショートカットキーを割り当てることもできますので、一つのキー操作で保存とビルドを同時に行いソースコードの変更を即時反映させることが可能です。もちろんビルドの時間はどうしても0にはできないためインタープリター言語と全く同じというわけにはいきませんが、最近のコンピュータであればビルド速度はかなり早いですので実際はほとんど問題にならないと思います。

Intellij IDEAのマクロ登録で、リフォーマットと保存を同時に行う | 徒然エンジニア日記

まとめ

  • Automatic restartとHotswapは別物。
  • Tomcatの再起動を伴わずにソースコードの更新をデバッグ実行中のアプリケーションに反映するにはJava VM + Intellij IDEAのHotswap機能を使う。
  • Hotswapにはspring-boot-devtoolsは使わない。Run ConfigurationのModify options > On ‘Update’ actionで設定する。
よかったらシェアしてね!
目次