JAVAの記事一覧

目次

Javassist -- Java バイトコードを操作するクラスライブラリ -- 入門

http://www.csg.is.titech.ac.jp/~chiba/notes/javapress03/index.html

JavassistのClassPool?オブジェクト

クラスパスを管理し、クラスファイルをディスク等から実際に読み込む

import javassist.*;

public class List2 {
   public static void main(String[] args) throws Exception {
       ClassPool cp = ClassPool.getDefault();
       CtClass cc = cp.get("java.awt.Point");
       CtClass superClass = cc.getSuperclass();
       System.out.println(superClass.getName());
   }
}

JavassistのCtClass?

リフレクションAPIのClassオブジェクトはJava仮想機械(JVM)にロード済みのクラスを表し、JavassistのCtClass?オブジェクトはまだロードされていない、クラスファイルの状態のクラスを表す。

クラス名を得る

String getName()

クラス名を変更

void setName(String name)

修飾子を得る

int getModifiers()

修飾子を変更

void setModifiers(int m)

スーパークラスを得る

CtClass? getSuperclass()

スーパークラスを変更

void setSuperclass(CtClass? c)

インタフェースを得る

CtClass?[] getInterfaces()

インタフェースを変更

void setInterfaces(CtClass?[] i)

全フィールドを得る

CtField?[] getFields()

フィールドを追加

void addField(CtField? f)

全メソッドを得る

CtMethod?[] getMethods()

メソッドを追加

サンプルコード

void addMethod(CtMethod m)  

import javassist.*;

public class List5 {
   public static void main(String[] args) throws Exception {
       ClassPool cp = ClassPool.getDefault();
       CtClass p3 = cp.get("Point3D");
       CtClass p2 = cp.get("java.awt.Point");
       p3.setSuperclass(p2);
       CtMethod m = CtNewMethod.make(
           "public String toString() {" +
           "  return \"(\" + x + \",\" + y + \",\" + z + \")\"; }", p3);
       p3.addMethod(m);
       p3.writeFile();
   }
}

コンストラクタを得る

CtConstructor?[] getConstructors()

コンストラクタを追加

void addConstructor(CtConstructor? c)

クラス定義をファイルに保存

void writeFile()

クラスをロード

Class toClass()

クラス定義の中身を得る

byte[] toBytecode()

CtMethod? のメソッド (抜粋)

メソッド名を得る

String getName()

メソッド名を変更

void setName(String name)

修飾子を得る

int getModifiers()

修飾子を変更

void setModifiers(int m)

メソッド本体を変更

void setBody(String src)

メソッドの一部を変更

void instrument(ExprEditor? e)

メソッドの最初にコードを挿入

void insertBefore(String src)

サンプルコード

変数m がCtMethod? オブジェクトを表しているとすると、

m.insertBefore("System.out.println(\"OK\");");

で m が表すメソッドの冒頭に println メソッドの呼び出しを挿入できます。

メソッドの呼び出しを、別メソッドの呼び出しに置換

サンプルコード

say メソッドの呼び出しを、hi メソッドの呼び出しに置換

public class Hello {
   public void say() {
       System.out.println("Hello");
   }
   public void hi() {
       System.out.println("Hi");
   }
   public static void main(String[] args) {
       System.out.println("start...");
       new Hello().say();
   }
}
import javassist.*;

import javassist.*;
import javassist.expr.*;

public class List8 {
   public static void main(String[] args) throws Exception {
       ClassPool cp = ClassPool.getDefault();
       CtClass cc = cp.get("Hello");
       CtMethod m = cc.getDeclaredMethod("main");
       m.instrument(new ExprEditor() {
           public void edit(MethodCall m) throws CannotCompileException {
               if (m.getClassName().equals("Hello")
                       && m.getMethodName().equals("say"))
                   m.replace("$0.hi();");
           }
       });
       cc.writeFile();
   }
}

元のメソッド呼び出しの相手オブジェクトを表す特殊変数 $0

上のプログラムでは、見つかった sayメソッドの呼び出し式を下の文で置換します。

$0.hi();

これは同じオブジェクトのhiメソッドの呼び出しを意味します。$0はJavassistが提供する特殊な変数で、メソッド呼び出し式を置換する場合、元のメソッド呼び出しの相手オブジェクトを表します。

メソッドを呼び出す前にメッセージを表示する

public class Hello {
   public void say() {
       System.out.println("Hello");
   }
   public void hi() {
       System.out.println("Hi");
   }
   public static void main(String[] args) {
       System.out.println("start...");
       new Hello().say();
   }
}
import javassist.*;

import javassist.*;
import javassist.expr.*;

public class List8 {
   public static void main(String[] args) throws Exception {
       ClassPool cp = ClassPool.getDefault();
       CtClass cc = cp.get("Hello");
       CtMethod m = cc.getDeclaredMethod("main");
       m.instrument(new ExprEditor() {
           public void edit(MethodCall m) throws CannotCompileException {
               if (m.getClassName().equals("Hello")
                       && m.getMethodName().equals("say"))
                   m.replace(”{ System.out.println("** say() **");” + 
                                     "$_ = $proceed($$); }”);
           }
       });
       cc.writeFile();
   }
}

置換される元の式に書かれていた処理を実行 $proceed

$proceedは、置換される元の式に書かれていた処理を実行するために使います。

   

置換される元のコードの処理を実行

$_ = $proceed($$);

元のコードの内容や、メソッドの名前、シグネチャにかかわらず、常に同じです。

元のメソッド呼び出し式の実引数列を表す特殊変数 $$

$1、$2、… を使っても同じ結果が得られますが、$$を使うと実引数列の個数にかかわらず常に$$と書けます。上の例でも、sayメソッドの引数はないので、$proceed() と明示的に引数を書かないようにしてもよいのですが、$proceed($$)と書いておけばJavassistが実引数の個数を考慮して適切にコンパイルしてくれます。

置換される元のコードの計算結果を表す特殊変数

$_は、置換される元のコードの計算結果を表す特殊変数です。上のブロックの実行終了後のこの変数の値が、置換後の新しいコードの計算結果となります。say メソッドの場合は戻り値の型がvoidなので、この変数に何を代入しても無視されますが、変数自体は利用可能です。$_の型は置換される元の式の型と同じです。元の式の型がvoid型の場合はObject型です。またこの場合、$proceedの戻り値はnullです。

実引数列を使って、別なメソッドをリフレクションAPIで呼び出す

Object result = m.invoke($args); 

実引数列をObject型の配列に変換 $args

キャストに置換前の型を入れる

$_ = ($r)result;

もし戻り値の型($_の型でもある)がintのような基本データ型のときは、Integerのようなwrapperクラスからint型へ値の変換をおこないます。

メソッドの末尾にコードを挿入

void insertAfter(String src, boolean asFinally)

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-07-31 (土) 07:41:09 (4101d)