WEBワークショップ

HOME > WEBワークショップ > Javassistチュートリアル



Javassistチュートリアル


    Javassistチュートリアルとは?
     
    Javassistは、詳しい知識を必要とせずにJavaバイトコードを変換することのできるライブラリです。最近注目されているアスペクト指向プログラミングを実現するためのAOPエンジンとして、オープンソースのJ2EEサーバであるJBossに採用され、現在はJBossのサブプロジェクトとして開発が進められています。

    「Javassistチュートリアル」は、開発者である東京工業大学 千葉滋 助教授により公開されている Javassist Home Page 内の "Getting Started with Javassist" を、
    Acroquest Technology 株式会社 小森 裕介が許可を得て日本語に翻訳したものです。

    Javassist Home Page : http://www.csg.is.titech.ac.jp/~chiba/javassist/
    Getting Started with Javassist : http://www.csg.is.titech.ac.jp/~chiba/javassist/tutorial/tutorial.html

    ※ 本稿は2004年10月27日現在公開されている原文を元に翻訳を行ったものです。
    ※ 原文の著作権は東京工業大学 千葉滋 助教授にあります。翻訳文の著作権はAcroquest Technology 株式会社 小森裕介にあります。
    ※ 本稿の一部または全部をいかなる手段においてもAcroquest Technology 株式会社の許可なく転載・複製することを禁じます。
    ※ 本稿の記述内容を利用することで被ったいかなる不利益に対しても、Acroquest Technology 株式会社及び小森裕介は責任を負いません。
     


1.バイトコードの読み書き

Javassistは、Javaバイトコードを扱うライブラリです。 Javaバイトコードはクラスファイルと呼ばれるバイナリファイルへ格納され、 1つのファイルが1つのクラスやインターフェースを格納しています。

Javassist.CtClassクラスは、このクラスファイルを抽象的に表しています。 CtClass (compile-time class)オブジェクトは、クラスファイルを扱うためのハンドルになっています。 以下のプログラムは、とてもシンプルな例です。

 ClassPool pool = ClassPool.getDefault();
 CtClass cc = pool.get("test.Rectangle");
 cc.setSuperclass(pool.get("test.Point"));
 cc.writeFile();


このプログラムでは、Javassist でのバイトコード変換を制御する ClassPool オブジェクトを取得しています。 この ClassPool オブジェクトはクラスを表す CtClass のコンテナとなっています。 ClassPool は CtClass オブジェクトを生成するため必要に応じて Class ファイルを読み込み、 生成したオブジェクトを後で使用できるように保持します。 クラスの定義を変更するためには、まずそのクラスを表す CtClass オブジェクトを ClassPool から取得してください。 そのためには ClassPool クラスの get() メソッドを利用することができます。 上に示したプログラムでは、test.Rectangle クラスを表す CtClass オブジェクトを ClassPool から取得し、変数 cc に格納しています。

実装の観点でいえば、ClassPool は クラス名をキーとした CtClass オブジェクトのハッシュテーブルになっています。 ClassPool の get() メソッドはキーに関連づけられた CtClass オブジェクトをハッシュテーブルから検索します。 該当する CtClass オブジェクトが見つからなかった場合、 get() メソッドは新しい CtClass オブジェクトを生成するためにクラスファイルを読み込み、ハッシュに格納した上で戻り値として返します。

ClassPool から得られる CtClass オブジェクトは変更することができます。 上の例では、test.Rectangle クラスのスーパークラスを test.Point クラスに変更しています。 変更結果は最終的に CtClass の writeFile() メソッドを呼び出すことで元のクラスファイルに反映されます。

writeFile() は CtClass オブジェクトをクラスファイルに変換し、ローカルディスクに書き出します。 また、Javassist は更新されたクラスのバイトコードを直接取得するメソッドも提供してます。 バイトコードを取得するには toBytecode() メソッドを呼び出します。

 byte[] b = cc.toBytecode();

●新たなクラスの定義

新たなクラスをゼロから定義するには、ClassPool クラスの makeClass() メソッドを呼び出します。

 ClassPool pool = ClassPool.getDefault();
 CtClass cc = pool.makeClass("Point");


このプログラムはメンバをまったく持たない Point クラスを定義します。 Point クラスのメンバメソッドは CtNewMethod クラスのファクトリメソッドを利用して生成し、 CtClass addMethod() で Point クラスに追加します。

makeClass() はインターフェスを生成することはできません。 その場合は makeInterface() メソッドを利用します。 インターフェースのメンバメソッドは CtNewMethod の abstractMethod() メソッドを使用して作成します。 インターフェースのメソッドは抽象メソッドであることに注意してください。

●クラスの凍結

writeFile()、toClass()、toBytecode() といったメソッドによって CtClass オブジェクトがクラスファイルに変換された場合、 Javassist はその CtClass オブジェクトを凍結します。凍結された CtClass オブジェクトはそれ以上変更はできません。

Javassist が CtClass オブジェクトを凍結するとき、そのオブジェクトに含まれるデータ構造も削除されます。 メモリ消費を少なくするため、Javassist はメソッドボディのようなデータ構造の一部を削除するのです。 その結果、CtClassオブジェクトが切り詰められたあとは、メソッド名やシグネチャへのアクセスはできますが、 メソッドのバイトコードへはアクセスすることができません。

CtClass オブジェクトの切り詰めを避けるには、前もって stopPruning() メソッドを呼び出しておきます。

 CtClasss cc = ...;
 cc.stopPruning(true);
       :
cc.writeFile(); // クラスファイルに変換される
// cc は切り詰められない


CtClass オブジェクトを切り詰めない場合、もう一度クラス定義を変更できるように凍結を解除することができます。

 CtClasss cc = ...;
 cc.stopPruning(true);
     :
 cc.writeFile();
 cc.defrost();
 cc.setSuperclass(...);    // 凍結解除されているので変更できる

●クラス検索パス

ClassPool の static メソッドである getDefault() を使って取得したデフォルト ClassPool は、 JVM(Java仮想マシン)と同じクラスパスを持ちます。 red,プログラムが JBoss や Tomcat のような Web アプリケーションサーバ上で動作している場合、 ClassPoolオブジェクトはユーザクラスを見つけることができません。 なぜならば、そのような Web アプリケーションサーバはシステムクラスローダと同じような複数のクラスローダを利用しているからです。 この場合、ClassPool へクラスパスを追加する必要があります。以下の例で、変数 pool は ClassPool オブジェクトの参照です。

 pool.insertClassPath(new ClassClassPath(this.getClass()));


この例では、this が示すオブジェクトのクラスをロードするために使われたクラスパスをClassPoolに登録しています。 this.getClass() の代わりに、どのような Class オブジェクトを利用することもできます。 Class オブジェクトが表すクラスをロードするために利用されたクラスパスが追加されるのです。

クラスパスにはディレクトリ名を登録することもできます。 たとえば、以下のコードでは '''/usr/local/javalib''' というディレクトリをクラスパスに登録しています。

 ClassPool pool = ClassPool.getDefault();
 pool.insertClassPath("/usr/local/javalib");


クラスパスには、ディレクトリ名だけでなく URL も登録できます。

 ClassPool pool = ClassPool.getDefault();
 ClassPath cp 
    = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
 pool.insertClassPath(cp);


このプログラムでは、http://www.javassist.org:80/java/というURLをクラスパスに追加しています。 この URL は '''org.javassist''' パッケージに属するクラスを検索する場合のみ使用されます。

さらに、ClassPool オブジェクトにバイト配列を直接渡して、 そこから CtClass オブジェクトを生成することもできます。 そのためには ByteArrayClassPath を使います。

 ClassPool cp = ClassPool.getDefault();
 byte[] b = バイト配列;
 String name = クラス名;
 cp.insertClassPath(new ByteArrayClassPath(name, b));
 CtClass cc = cp.get(name);


ここで得られた CtClass オブジェクトは、バイト配列 b が持つクラスファイルのクラスを表しています。 ClassPool は get() メソッドが呼ばれると、 与えられた ByteArrayClassPath から引数の示すクラスのクラスファイルを読み込みます。

もし、クラスの完全限定名がわからない場合は ClassPool の makeClass() メソッドを利用することができます。

 ClassPool cp = ClassPool.getDefault();
 InputStream ins = クラスファイルを読み込むための InputStream;
 CtClass cc = cp.makeClass(ins);


makeClass() は 与えられた InputStream から生成した CtClass オブジェクトを返します。 makeClass() メソッドを使う方法は、ClassPool に多くのクラスファイルを読み込ませる場合に有効です。 これは、クラス検索パスに大きな Jar ファイルが含まれている場合にパフォーマンスを改善することができるからです。 ClassPool オブジェクトは必要に応じてクラスファイルを読み込むため、 クラスファイルを読み込むたびに Jar ファイル全体を検索します。 makeClass() メソッドは検索の最適化に利用できるのです。 makeClass() メソッドによって生成された CtClass オブジェクトは ClassPool オブジェクトに保持され、 クラスファイルは二度と読み込まれません。

ユーザはクラス検索パスを拡張することができます。 ClassPath インターフェースを継承する新たなクラスを作成し、 ClassPool の insertClassPath() メソッドへ作成したクラスのインスタンスを渡すのです。 これにより、通常以外のリソースをクラス検索パスへ含めることができます。


1 2 3 4 5

ページトップへ
Java? は、米国Sun Microsystems,Inc. の登録商標です。
原著:Copyright c 2000-2005 O’Reilly Media, Inc. All rights reserved.
邦訳:Copyright c 2005 Acroquest, Co.,Ltd. All rights reserved.