Hot swapping を利用するには Spring Loaded が使える。
Spring Loaded
Spring Loaded はJVMの停止なしにクラスファイルの変更を行う hot code replace ツール。
デバック実行時にメソッドの中身を書き換える hot swap では、メソッドの中身の書き換えだけしかできない。 Spring Loaded では メソッドやフィールド、コンストラクタの追加/変更/削除ができる。
Hot Swap ? Hot Deploy?
Hot Swap は java 1.4 で Java Platform Debugger Architecture (JPDA) としてJVMに追加された機能。Debug モードで起動した JVM に外部から変更コードを送り込んでクラスのメソッドの中身を書き換えられる IDE のデバッグモードでよくある例のやつ。クラス構造を壊すようなメソッド追加やフィールド追加などはできず、あくまでメソッドボディのみが書き換え対象となる。
S2界隈の Hot Deploy とか アプリケーションコンテナの Hot Reload とか言われるのは、クラスローダを破棄することでクラスローダ配下のクラスをアンロード対象として、別バージョンのクラスを読み込む。S2 の Hot Deploy なんかは、リクエスト毎にクラスローダを破棄してたりする。 アプリケーションが必要な全クラスまるっと読み直すので、初期化のコストなどがネックになることがある。
Spring Loaded では
Instrumentation API を使って、クラスのロード時に agent をかまして、後のリロードに備えるように ASM 使ってバイトコード書き換えてる。 あらかじめディスパッチ用のバイトコードを仕込んでおいて、リロード時にディスパッチ先を変えてあげているみたい。
java -javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify SomeJavaClass
-javaagent 指定して、かつ -noverify(JVM側でクラスファイルの検証を無効)と。
・・・
Premain-Class: org.springsource.loaded.agent.SpringLoadedAgent
Agent-Class: org.springsource.loaded.agent.SpringLoadedAgent
Can-Redefine-Classes: true
MANIFEST.MF で指定されている agent はorg.springsource.loaded.agent.SpringLoadedAgent。実装みるとかなりしんどそう。
話が脇道にそれたので戻すと
Gradle の設定
こんな感じでビルドスクリプトに springloaded の依存追加する。
buildscript { repositories { maven { url "https://repo.spring.io/libs-release" } mavenLocal() mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.1.RELEASE") classpath("org.springframework:springloaded:1.2.0.RELEASE") } }
Gradle と IntelliJ で開発する場合には、IntelliJ による class ファイルの出力先を Gradle のそれに合わせるため、idea プラグインの設定を変更する。
apply plugin: 'idea' idea { module { inheritOutputDirs = false outputDir = file("$buildDir/classes/main/") } }
Intellj の設定
Intellij の設定で、Make Project Automatically
を有効にする。
以下で起動すると、編集が即座に反映される。
gradle bootRun
静的ファイル
静的コンテンツのテンプレートのキャッシュ設定を変更。
- Thymeleafテンプレート - spring.thymeleaf.cache を false
- FreeMarkerテンプレート - spring.freemarker.cache を false
- Groovyテンプレート - spring.groovy.template.cache を false
- Velocityテンプレート - spring.velocity.cache を false