Spring Bootで、コンパイル後のクラスの変更を反映するHot Reloadingというものができるという、Spring Loadedを試してみました。
Scalaで。
Spring Boot+Spring Loaded
用意したpom.xmlはこのような感じ。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.littlewings</groupId> <artifactId>spring-boot-loaded</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.1.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> <version>1.2.1.RELEASE</version> </dependency> </dependencies> </plugin> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>3.2.0</version> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <scalaVersion>${scala.version}</scalaVersion> <args> <arg>-Xlint</arg> <arg>-unchecked</arg> <arg>-deprecation</arg> <arg>-feature</arg> </args> <recompileMode>incremental</recompileMode> </configuration> </plugin> </plugins> </build> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <scala.major.version>2.11</scala.major.version> <scala.version>${scala.major.version}.5</scala.version> </properties> </project>
Spring BootのMavenプラグインの依存関係に、Spring Loadedが入ることがポイントみたいです。
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> <version>1.2.1.RELEASE</version> </dependency> </dependencies> </plugin>
あとは、アプリケーションを実装します。Spring MVCで。
アプリケーションのエントリポイント。
src/main/scala/org/littlewing/spring/loaded/App.scala
package org.littlewings.spring.loaded import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication object App { def main(args: Array[String]): Unit = SpringApplication.run(classOf[App], args: _*) } @SpringBootApplication class App
RestController。
src/main/scala/org/littlewing/spring/loaded/HelloController.scala
package org.littlewings.spring.loaded import org.springframework.web.bind.annotation.{ RequestMapping, RestController } @RestController class HelloController { @RequestMapping(Array("/message")) def message: String = "Hello World!!" // "こんにちは、世界!!" }
すでにコメントアウトが見えていますが、これをHot Reloadingで変更を反映してみます。
アプリケーションを起動。
$ mvn spring-boot:run
動作確認。
$ curl http://localhost:8080/message
Hello World!!
「Hello World!!」が返ってきました。
次に、RestControllerのソースコードを修正して
@RequestMapping(Array("/message")) def message: String = // "Hello World!!" "こんにちは、世界!!"
アプリケーションを起動したまま、別のターミナルでコンパイル。
$ mvn compile
すると、起動中のSpring Bootアプリケーションが変更を検知するようで
2015-01-31 00:27:00.417 INFO 91345 --- [Loader@58644d46] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/message],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String org.littlewings.spring.loaded.HelloController.message() 2015-01-31 00:27:00.419 INFO 91345 --- [Loader@58644d46] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest) 2015-01-31 00:27:00.420 INFO 91345 --- [Loader@58644d46] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
確認してみると、変更が反映されています。
$ curl http://localhost:8080/message
こんにちは、世界!!
できましたね。
継続的コンパイル
先ほどは、もうひとつのターミナルで「mvn compile」を実行しましたが、今度はscala-maven-pluginにソース変更を検知してもらい、自動的にコンパイルされるようにしてみます。
Continuous Compilation of Scala sources
http://davidb.github.io/scala-maven-plugin/example_cc.html
scala:cc
http://davidb.github.io/scala-maven-plugin/cc-mojo.html
起動してみます。
$ mvn scala:cc
このまま、ターミナルに制御が戻らず、ソースコードの変更を待っている状態になります。
[INFO] wait for files to compile...
終了する時は、Ctl-Cなどで。
あとは、ソースコードを変更すればそれを検知して、自動的にコンパイルしてくれます。その後は、再びソースコードの変更待ちとなります。
なお、デフォルトではfscを使用するみたいで、fscを使わない場合はこうするらしいです。
$ mvn scala:cc -Dfsc=false
なんですが、なんか変化が見えないなーと思ったら、インクリメンタルコンパイルモードだとfscを使わないようになっているみたいです。
https://github.com/davidB/scala-maven-plugin/blob/3.2.0/src/main/java/scala_maven/ScalaContinuousCompileMojo.java#L133
pomでrecompileModeをコメントアウトして
<!-- <recompileMode>incremental</recompileMode> -->
普通に起動すると、
$ mvn scala:cc
fscを使うよ、とメッセージが表示されるようになり
[INFO] use fsc for compilation
「-Dfsc=false」にするとメッセージが出なくなりました。
$ mvn scala:cc -Dfsc=false
なるほど。