作成 2004/9/9
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はクラスファイルを編集するものです。既存のクラスを編集してもいいですが、ここでは次のような簡単なクラスを操作してみます。なお、インターフェイスは必須ではありませんが、とりあえずつけときます。
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()しても編集されたクラスのインスタンスになります。
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