WEBワークショップ

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



Javassistチュートリアル


2.クラスプール

ClassPool オブジェクトは CtClass オブジェクトのコンテナです。CtClass オブジェクトが一度生成されると、そのオブジェクトは ClassPool に永久に保持されます。これは、Java コンパイラがあとでCtClass の示すクラスをコンパイルするときに、CtClass オブジェクトへアクセスする必要があるからです。

たとえば、Point クラスを表す CtClass オブジェクトに新しいメソッド getter() が追加されたとしましょう。その後、そのプログラムがPoint クラスの getter() メソッドの呼び出しを含むソースコードをコンパイルして、コンパイル後のコードを別のクラス Line のメソッド本体として使用するとします。もし、 Point クラスを表す CtClass オブジェクトが失われていたら、コンパイラは getter() メソッドの呼び出しを含むコードをコンパイルすることができません。オリジナルのクラス定義には getter() メソッドが含まれていないのです。そのため、そのようなメソッド呼び出しを正しくコンパイルするには、プログラムを実行している間常に ClassPool が全ての CtClass インスタンスを保持していなければならないのです。

●メモリ不足を回避する

このような ClassPool の仕様では、多くの CtClass オブジェクトが非常に大きくなった場合、膨大な量のメモリを消費しています(このような現象は Javassist が様々な方法でメモリ消費を抑える試みをするようになってからは、滅多に発生していません)。この問題を避けるためには、不要になった CtClass オブジェクトを ClassPool から明示的に取り除く必要があります。CtClass の detach() メソッドを呼び出すと、その CtClass オブジェクトは ClassPool から削除されます。

 CtClass cc = ... ;
 cc.writeFile();
 cc.detach();

detach() を呼び出した後は、CtClass オブジェクトのどのようなメソッドをも呼び出してはいけません。

別の方法としては、時折古い ClassPool を破棄して新たなものに置きかえるという方法もあります。古い ClassPool がガーベッジ・コレクションによって回収されるとき、そこに含まれていた CtClass オブジェクトも一緒に回収されます。新しい ClassPool のインスタンスを生成するには、以下のようなコードを書きます。

 ClassPool cp = new ClassPool();
 cp.appendSystemPath();// 
 または appendClassPath() によって別のクラスパスを追加する


これは ClassPool.getDefault() によって返されるデフォルト ClassPool と同じような ClassPool オブジェクトを生成します。ClassPool.getDefault() は、便宜上提供されるシングルトン・ファクトリメソッドです。そのため、デフォルト ClassPool は決してGCによって回収されません。

●ClassPoolの親子関係

ClassPool オブジェクトは java.lang.ClassLoader と同じように子を持つことができます。

 ClassPool parent = ClassPool.getDefault();
 ClassPool child = new ClassPool(parent);
 child.insertClassPath("./classes");


上の例では、child.get() が呼ばれると子の ClassPool は最初に親の ClassPool へ処理を委譲します。親の ClassPool がクラスファイルの検索に失敗したとき、子の ClassPool は ./classes ディレクトリ配下から検索を試みるのです。

child.childFirstLookup が true に設定されていると、子の ClassPool は親へ処理を委譲する前にクラス検索を行います。

 ClassPool parent = ClassPool.getDefault();
 ClassPool child = new ClassPool(parent);
 child.appendSystemPath();         // デフォルト ClassPool と同じパスを持たせる
 child.childFirstLookup = true;    // 子 ClassPool の振る舞いを変える

●クラス名の変更による新たなクラスの定義

writeFile() や toBytecode() メソッドによって、一度 CtClass オブジェクトがクラスファイルへ変換されると、Javassist はそれ以降の CtClass への変更を拒否します。そのため、 Point クラスを表す CtClass オブジェクトがクラスファイルに変換された後は、Point クラスに対する setName() メソッドが実行できないため、Point クラスのコピーとして Pair クラスを定義することはできません。つまり、以下のコードは誤りとなります。

 ClassPool pool = ClassPool.getDefault();
 CtClass cc = pool.get("Point");
 cc.writeFile();
 cc.setName("Pair");    // writeFile() が既に呼び出されているため、実行できない


このような制限を避けるには、ClassPool の getAndRename() メソッドを呼び出します。

 ClassPool pool = ClassPool.getDefault();
 CtClass cc = pool.get("Point");
 cc.writeFile();
 CtClass cc2 = pool.getAndRename("Point", "Pair");


getAndRename() が呼ばれると、Point クラスを表す新しい CtClass オブジェクトを生成するために、ClassPool は最初に Point.class を読み込みます。しかし、ClassPool は CtClass をハッシュテーブルに格納する前に、その名前を Point から Pair に変えるのです。そのため、Point クラスを表す CtClass オブジェクトの writeFile() や toBytecode() メソッドが呼ばれた後でも getAndRename() メソッドを実行することができるのです。


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.