Javassistにさわる

作成 2004/9/9

Javassistにさわってみたメモ。ちょっとだけ。

Javassistとは?

Javaassistとは、Javaバイトコードエンジニアリングライブラリです。カタカナにするとなんですが、クラスファイルを読みこんで編集するツールです。現在はJBossプロジェクトのサブプロジェクトとして開発されており、JBossAOPのコアとなる技術です。Javassistを作っているのは、千葉滋さんという方らしいです。ちなみに名前は、JavaAssistでなく、Javassistです。

JavassistのWebページ
http://www.csg.is.titech.ac.jp/~chiba/javassist/

インストール

JavassistはsourceforgeのJBossダウンロードページからダウンロードします。

Javassistのダウンロード
https://sourceforge.net/projects/jboss/

ここでは、Javassist 3.0 Beta2をダウンロードしました。

JavassistはJARファイルとして提供されています。ダウンロードしたアーカイブを解凍し、中のjavassist.jarをクラスパスに通せば利用できます。

最初のJavassist

Javassistはクラスファイルを編集するものです。既存のクラスを編集してもいいですが、ここでは次のような簡単なクラスを操作してみます。なお、インターフェイスは必須ではありませんが、とりあえずつけときます。

IFoo.java

package hoge;

public interface IFoo {
    public void doSomething();
}

Foo.java

package hoge;

public class Foo implements IFoo{

    public void doSomething(){
        System.out.println("doSomethig");
    } 
}
そして、次のクラスがJavassistを利用してメソッドに前処理を追加した例です。
package hoge;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

public class Sample1 {

    public static void main(String[] args) throws Exception{
        
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("hoge.Foo");
        CtMethod method = cc.getDeclaredMethod("doSomething");
        method.insertBefore("System.out.println(\"before\");");
        Class clazz = cc.toClass();
        IFoo foo = (IFoo)clazz.newInstance();
        foo.doSomething();
    }
}

実行するとbefore出力も表示されます。

before
doSomethig

クラスローダー

前の例の

IFoo foo = (IFoo)clazz.newInstance();

は、

Foo foo = (Foo)clazz.newInstance();

にはキャストできません。ClassCastExceptionになります。なぜなら、cc.toClass()で取得したFooクラスとソース中にFooとそのまま記述したFooクラスのクラスローダが異なるからです。Javaでは、同じクラス名でもクラスローダが異なると異なるクラスとされます。

System.out.println(clazz.getClassLoader());
System.out.println(Foo.class.getClassLoader());

というコードを実行すると、次のように表示され、クラスローダーが違うことが分かります。JavaAssistのClassPoolは内部にクラスローダーを持っています。

javassist.ClassPool$SimpleLoader@c2a132
sun.misc.Launcher$AppClassLoader@1050169

new Foo()したときに、Javassistのクラス操作を行いたい場合には、new Foo()するクラスローダーをJavassistのクラスローダにする必要があります。Sample1クラスをJavassistでロードします。例えばこんな感じ。

Launcher.java

package hoge;

import java.lang.reflect.Method;

import javassist.ClassPool;

public class Launcher {

    public static void main(String[] args) throws Exception{
        //Class clazz = Class.forName("hoge.Sample1");
        ClassPool pool = ClassPool.getDefault();
        Class clazz = pool.get("hoge.Sample1").toClass();
        Method method = clazz.getMethod("main", new Class[]{String[].class});

        method.invoke(null, new Object[]{args});
    }
}

これで、Sample1で、FooにキャストしてもClassCastExceptionにはなりません。というか、ClassPoolから取得せず、new Foo()しても編集されたクラスのインスタンスになります。

その他

Webアプリケーションのクラスローダーを差し替えるには?

mainからはじまるJavaアプリケーションは、自分の好き放題なので、クラスローダーの差し替えも可能ですが、Webアプリケーションなどのコンテナ上で動くものは、クラスローダーの差し替えはどうするのでしょうか?

....

分かりません。アプリケーションサーバ側で、そのような差込口がないと難しそうな気がします。あるいは、Webアプリケーションのランチャー部分でJavassistのクラスローダーを使えば差し込めそうですが、一般に個々のWebアプリケーションのクラスローダーはランチャーのローダーから分割して利用されることが多いので、ランチャー側のローダーからWebアプリケーションのクラスが見えないという事態も起こります。JBossAOPを調べるとわかるかなぁーと。

インスタンス単位の編集

クラス単位の差込でなく、インスタンス単位の差込みたいなのはできるのでしょうか?これまた分かりません。

もうちょっと調べようかと思いましたが眠いので今度にしましょう。淡白ですね。

参考

Javassist Tutorial
http://www.csg.is.titech.ac.jp/~chiba/javassist/tutorial/tutorial.html

Javassistメモ
http://www.ncfreak.com/asato/doc/javassist.html


Top