無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Javassistを使って実行時のクラス拡張をやってみた。

Javassistを使うと、実行時のクラス拡張(コンパイル済みのクラスをJavaプログラム実行時に改変し、メソッドに処理を追加したり、フィールドを追加したりすること。)ができます。以下は簡単なサンプルとはまったところについてのメモです。

簡単なサンプル

"com.example.Kitten"を"名前を話すcom.example.Kitten"に拡張してみます。基本的な手順は次の通り。Javassistは3.4を使用しています。

  1. ClassPoolを作る。
  2. CtClassを作る。
  3. CtClassのAPIを呼び出し、クラスを拡張する。
  4. CtClass#toClass(ClassLoader, ProtectionDomain)でClassLoaderに拡張したクラスを読み込ませる。
// ClassPoolを得る。
ClassPool cp = new ClassPool();
cp.appendSystemPath();

// 拡張するクラスを指定してCtClassを取得する。
CtClass cc = cp.get("com.example.Kitten");

// 拡張するメソッドを取得し、修正。
CtMethod m = cc.getDeclaredMethod("meow");
m.setBody("System.out.println( this.name +  \" : meow!\" );");

// ClassLoaderに拡張したクラスを読み込ませる。
Class c = cc.toClass(
    ClassLoader.getSystemClassLoader(),
    Sample.class.getProtectionDomain());

// インスタンスを生成。メソッドを呼び出してみる。
Kitten mii = (Kitten) c.newInstance();
mii.setName( "mii" );
mii.meow(); // "mii : meow!" が表示される。

拡張前のKittenクラスは以下。

package com.example;

/**
 * 猫。
 *
 * @version $Revision:$
 * @author  $Author:$
 */
public class Kitten {

    /**
     * 名前
     */
    private String name = "";

    /**
     * 年齢
     */
    private int age = 0;

    /**
     * コンストラクタ
     */
    public Kitten (){}

    /**
     * コンストラクタ
     *
     * @param name
     *        名前
     * @param age
     *        年齢
     */
    public Kitten (
        String name,
        int age ) {
        this.name = name;
        this.age = age;
    }

    /**
     * 名前を取得する。
     * @return 名前
     */
    public String getName () {
        return name;
    }
    /**
     * 名前を設定する。
     * @param name 名前
     */
    public void setName ( String name ) {
        this.name = name;
    }

    /**
     * 年齢を取得する。
     * @return 年齢
     */
    public int getAge () {
        return age;
    }
    /**
     * 年齢を設定する。
     * @param age 年齢
     */
    public void setAge ( int age ) {
        this.age = age;
    }
    /**
     * 鳴く
     */
    public void meow( ) {
        System.out.println( "meow!" );
    }
}

実行結果です。

mii : meow!

クラスの置き換え

CtClass#toClass(ClassLoader, ProtectionDomain)を呼び出すと、指定したClassLoaderに拡張したクラスを読み込ませます。これ以降、引数で指定したクラスローダーを使って改変前のクラスをnewすると改変されたクラスが生成されます。なお、CtClass#toClass(ClassLoader, ProtectionDomain)を呼び出す前に、改変前のクラスがロードされているとエラーになります。ご注意。

ClassPool cp = new ClassPool();
cp.appendSystemPath();
CtClass cc = cp.get("com.example.Kitten");
CtMethod m = cc.getDeclaredMethod("meow");
m.setBody("System.out.println( this.name +  \" : meow!\" );");
Class c = cc.toClass(
    ClassLoader.getSystemClassLoader(),
    Sample.class.getProtectionDomain());

// インスタンスを生成。メソッドを呼び出してみる。
Kitten mii = (Kitten) c.newInstance();
mii.setName( "mii" );
mii.meow(); // "mii : meow!" が表示される。

// このクラスは、toClass()の引数で指定したSystemClassLoaderでロードされているので、
// new した場合も拡張したクラスが使われる。
Kitten tora = new Kitten("tora", 0);
tora.meow();  // "tora : meow!" が表示される。

setBody()で指定した処理の中で、メソッドに渡された引数を参照したい。

「$1」など、Javassist内部のコンパイラで処理される記号を使用します。チュートリアルの中に情報があります Javassistチュートリアル - 4.イントロスペクションとカスタマイズを参照。

ClassPool cp = new ClassPool();
cp.appendSystemPath();
CtClass cc = cp.get("com.example.Kitten");
CtMethod m = cc.getDeclaredMethod("setName",
    new CtClass[] { cp.get( "java.lang.String" ) });

// insertAfter()で指定する処理内でメソッドに渡された引数を使う。
// $1で最初の引数を取得。1から始まるので注意。
m.insertAfter("System.out.println( \"set name : \" + $1 );");

プリミティブ型のCtClassを得る

プリミティブ型のCtClassはCtClassの定数で定義されています。

CtClass boolCc = CtClass.booleanType;

配列型のCtClassを得る

配列型のCtClassは、ClassPool.get(String)で取得できます。プリミティブ型の配列も同様の手順で作成できます。

CtClass kittenArrayCc = cp.get( "com.example.Kitten[]" );
CtClass byteArrayCc = cp.get( "byte[]" );


参考: