JRebelを使った動的クラスリローディング
「あばばばばばばばば」
はい、この記事は、「じゃば あばばばば かれんだー - 邪 2010」の9日目、12/21日のものですのよ?
昨日は、id:nekopのBytemanによるJava黒魔術 - nekopの日記でした。このBytemanが利用している「Java(TM) java.lang.instrument」を利用したもうひとつの例として、JRebelというプロダクトを利用した動的なクラス再ローディングについて、ちょいと紹介しようと思いますのよ奥さん?
そもそもjava.lang.instrument APIってなんぞ?
Instrument APIは、監視/観察の対象となるアプリケーションのバイトコードをほかのバイトコードに置き換える(BCIを行う)ための枠組みを提供する。置き換えの方法としては、以下の2とおりが提供されている。
●クラスがロードされる過程に割り込み、そのバイトコードをほかのものに置き換える(以下、この方法を「割り込みによる置き換え」と呼ぶ)
http://www.itarchitect.jp/technology_and_programming/-/51050-3.html
●ロード済みのバイトコードがあったとして、それを置き換える新しいバイトコードをロードし直す(以下、この方法を「再ロードによる置き換え」と呼ぶ)※4
ようは、クラスロード時のフックを提供するAPIってことですね。クラスがロードされるタイミングで割り込み、バイトコードをごにょごにょできるようになったと。ただ、java.lang.instrument APIにはバイトコード操作をするAPIは提供されないので、そこはjavassistなりを利用して加工する必要があるわけですわ。
@skrb さんの記事がわかりやすいです。
JRebelってなんぞ?
ぶっちゃけていうと、クラスファイルが変更されたら再読み込みしてくれるものです。Seasar2のHotDeployとかのアレです。
Seasar2のHotDeployはServletFilterなどでリクエスト毎にクラスローダーを作成することで実現してますが、JRebelはJDK5から導入されたjava.lang.instrument APIを利用してクラスファイルの変更を監視して再ロードしてくれる仕組みです。
で、このJRebelですが、有償のソフトウエアです。じゃあ買わないと使えねーじゃん、ってことになりますが、実はScalaのデベロッパにはライセンスが無償提供されてて、Scalaで使うぶんには無償で利用できますやったね!Javaで使いたい人は買ってください☆
JRebelダウンロー丼とインスコール
で、以下のURLに名前とかメルアドとか入れると30日間の試用版がダウンロー丼できますよ。
JRebel Download Page | zeroturnaround.com
Installerもありますが、とりあえずzipで落として適当なとこに展開します。次に、Scala用のライセンスを以下のURLの"the free license itself"から落として、展開されたディレクトリに配置します。
Scala Goes Dynamic with JavaRebel | zeroturnaround.com
で、環境変数REBEL_HOMEに展開されたディレクトリを、PATHに$REBEL_HOME/binを追加します。.zshrcとかに書いておけばいいんじゃない?
export REBEL_HOME=~/dev/Tools/jrebel-3.5 export PATH=$REBEL_HOME/bin:$PATH
使ってみる
じゃあ、Scalaでやってみましょうね。こんなかんじで、ボタンを押す度に「あばばばばばばばば」と表示するだけのアプリケーションを作ります。Main.scalaって名前にしておきます。
import scala.swing._ import scala.swing.event._ object Main extends SimpleSwingApplication { def top = new MainFrame { title = "JRebel Test"; contents = new BorderPanel { import BorderPanel.Position._ add(label, Center) add(button, South) } } val button = new Button("押すのです") object label extends Label("あ") { listenTo(button) reactions += { case ButtonClicked(button) => text = text + "ば" } } }
で、JRebelでの再ロード対象のクラスパスなどを設定したxmlファイルをrebel.xmlというファイル名で用意します。dir nameには上記のscalaファイルと同じディレクトリにおいてくださいね。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <application> <classpath> <dir name="/Users/ozaki/sandbox/scala/yuroyoro/jrebel-test"/> </classpath> </application>
java.lang.instrument APIでJRebelを使うように、環境変数JAVA_OPTSに以下のように設定しておきます。
$ export JAVA_OPTS="-javaagent:${REBEL_HOME}/jrebel.jar"
で、scalacコマンドでコンパイルして、アプリケーションを起動します。
$ scalac Main.scala $ scala Main
アプリが起動します。とりあえずあばあば言ってますね。
で、アプリを起動したまま、Main.scalaでボタンをクリックする処理を以下のように修正します。
reactions += { case ButtonClicked(button) => text = text + "メリークリスマス!!" }
で、このままscalacコマンドでコンパイルして、もう一度ボタンを押してみると…
変更が反映されてますね。文章だと伝わりにくいと思うので、再ロードの様子がよくわかる動画を貼っておきます。
http://www.screencast.com/t/FBnFTlskdsr
はい、これでおしまいです。明日は、id:daisuke-m デス。