( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

JRebelを使った動的クラスリローディング

「あばばばばばばばば」


はい、この記事は、「じゃば あばばばば かれんだー - 邪 2010」の9日目、12/21日のものですのよ?


昨日は、id:nekopBytemanによるJava黒魔術 - nekopの日記でした。このBytemanが利用している「Java(TM) java.lang.instrument」を利用したもうひとつの例として、JRebelというプロダクトを利用した動的なクラス再ローディングについて、ちょいと紹介しようと思いますのよ奥さん?

そもそもjava.lang.instrument APIってなんぞ?

Instrument APIは、監視/観察の対象となるアプリケーションのバイトコードをほかのバイトコードに置き換える(BCIを行う)ための枠組みを提供する。置き換えの方法としては、以下の2とおりが提供されている。

●クラスがロードされる過程に割り込み、そのバイトコードをほかのものに置き換える(以下、この方法を「割り込みによる置き換え」と呼ぶ)
●ロード済みのバイトコードがあったとして、それを置き換える新しいバイトコードをロードし直す(以下、この方法を「再ロードによる置き換え」と呼ぶ)※4

http://www.itarchitect.jp/technology_and_programming/-/51050-3.html

ようは、クラスロード時のフックを提供するAPIってことですね。クラスがロードされるタイミングで割り込み、バイトコードをごにょごにょできるようになったと。ただ、java.lang.instrument APIにはバイトコード操作をするAPIは提供されないので、そこはjavassistなりを利用して加工する必要があるわけですわ。


@skrb さんの記事がわかりやすいです。


J2SE 5.0 Tiger 虎の穴 Instrumentation

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って名前にしておきます。


f:id:yuroyoro:20101221170628p:image

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

アプリが起動します。とりあえずあばあば言ってますね。


f:id:yuroyoro:20101221170628p:image


で、アプリを起動したまま、Main.scalaでボタンをクリックする処理を以下のように修正します。

    reactions += {
      case ButtonClicked(button) =>
        text = text + "メリークリスマス!!"
    }

で、このままscalacコマンドでコンパイルして、もう一度ボタンを押してみると…


f:id:yuroyoro:20101221170629p:image


変更が反映されてますね。文章だと伝わりにくいと思うので、再ロードの様子がよくわかる動画を貼っておきます。


http://www.screencast.com/t/FBnFTlskdsr


はい、これでおしまいです。明日は、id:daisuke-m デス。