Skip to main content

One post tagged with "JRebel"

View All Tags

· 5 min read
前回の投稿でIDEAとsbt-appengineが連携してくれたのは嬉しいけど、ソースを修正するたびにappserverの再起動は面倒!! 出来ることなら、IDEAでコーディング→ブラウザ確認→コーディング→...と繰り返したい。

こちらも実現できたので、方法を紹介。 やったことはこれだけ。

  • JRebelでクラスリローディング
  • クラスファイルの出力先をwebapp配下にする
  • sbtでソースコード監視

環境を簡単に書いておくと、IDEA+sbt(appengine plugin)という組み合わせ。 MyProjectがあって、その下にSubProject1, SubProject2がある構成。このMyProjectとかSubProjectはsbtの管理下にある状態。

まずはJRebelの導入から。 JRebel自体は有償だけど、Scala開発者にはライセンスを提供してくれてるのでこれを活用。 ■ ZeroTurnaround

http://sales.zeroturnaround.com/jrebel.jarを適当な場所に置く。ライセンスファイルもjarと同じところに置いておく。

JRebelでクラスリローディング

クラスリローディング対象とするディレクトリを指定するために、rebel.xmlを作成する。 MyProject/src/test/resources/rebel.xml
<?xml version="1.0"?>
<application>
<classpath>
<dir name="/Users/ken/workspace/MyProject/SubProject1/target/scala_2.8.1/classes" />
<dir name="/Users/ken/workspace/MyProject/SubProject2/target/scala_2.8.1/classes" />
</classpath>
</application>

続いて、sbtでJRebelを有効にする。 sbt-appengine-pluginではJREBEL_JAR_PATHを指定するだけでJRebelが有効になるはずなんだけど、IDEAのsbtコンソールでは環境変数を見てくれないらしいので、直接指定します。 MyProject/project/build/Project.scala

override def scanDirectories = Nil
override val devAppserverJvmOptions = List("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,address=2011,suspend=y") ++ List("-noverify", "-javaagent:/Users/ken/dev/jrebel/jrebel.jar") ++ super.devAppserverJvmOptions

JRebelに関係するのは

List("-noverify", "-javaagent:/Users/ken/dev/jrebel/jrebel.jar")

の部分。 ちなみに、XdebugとかはIDEAで接続するための記述。

クラスファイルの出力先をwebapp配下にする

JRebelの設定も終わったので、あとはIDEAからsbtを実行すればいいんだけど、sbtの出力先がappengineの出力先(webapp)とは異なっているためsbtの "~ compile" で監視をしても意味が無い。

そこで、SubProjectの出力先をwebapp配下にします。具体的には、MyProject/SubProject1/target/scala_2.8.1/classesを削除して、MyProject/target/scala_2.8.1/webapp/WEB-INF/classesにシンボリックリンクを張る。 手動でシンボリックリンクを張ればいいんだけど、sbt cleanを実行すると再度やり直さなければならないので、symlinkアクションを作った。 MyProject/project/build/Project.scala

import java.lang.Runtime
lazy val symlinkInstance = new Symlink
lazy val symlink = symlinkAction
def symlinkAction = task { args => task { symlinkInstance(args); None } }
class Symlink() extends Runnable {
def run() = {
def exec(command:String) = Runtime.getRuntime.exec(command)
val subs = Set("SubProject1", "SubProject2")
val webapp = outputPath / "webapp" / "WEB-INF" / "classes"

log.info("Creating directory %s".format(webapp))
exec("mkdir -p %s".format(webapp))

subs.foreach { sub =>
log.info("Processing for %s.".format(sub))
val path = "%s/%s".format(sub, mainCompilePath)
val command = "ln -s ../../../%s %s".format(webapp, path)

log.info("Removing directory %s".format(path))
exec("rm -fr %s".format(path))

log.info("Creating symlink %s -> %s".format(path, webapp))
exec(command)
log.info("")
}
None
}

def apply(args:Seq[String]):Option[String] = {
new Thread(this).start()
None
}

}

コード中の val subs = Set("SubProject1", "SubProject2")" を各自の環境に合わせてください。

sbt reload後に "symlink" アクションが追加されているので、実行してパスを確認して下さい。サブプロジェクトのclassesがシンボリックリンクになっているはず。

使い方

  1. sbtで "symlink" を実行。
  2. IDEAでデバッガ実行(=sbtも起動。前回の投稿参照)
  3. sbtで "~ compile" を実行

簡単に仕組みを説明。 symlinkを実行することによって、appserverの管理下にクラスファイルを出力するようになります。単にシンボリックリンクを張るだけなので初回だけ。sbt cleanしたときは再度実行します。 続いて、IDEAでデバッガを起動し、sbtの "~ compile" を実行してソースファイルを監視します。ソースコードに修正があった場合はsbtの監視によってコンパイルされ、クラスファイルが生成されます(生成先はsbt symlinkによってappsever管理下になっている)。 ブラウザからアクセスすると、JRebelによってクラスの再読込が行われ、sbtには "JRebel: Reloading class 'package.path.to.ClassName'." と出力されます。

これで、IDEAからsbtを起動し、ブラウザで確認しつつコードを書いていくことが出来る♪