Java
javassist

Javaでメタプログラミングをしてみた


はじめに

javassistというメタプログラミングが出来るライブラリを使用して

実際に作成したクラスを書き換えてみました。

ライブラリを使っていてクソだと思った時に使えます。

(謎のログが出力されるなど...)


ライブラリをインポート

gradleを使用しています。


build.gradle

...

dependencies {
...
compile group: 'org.javassist', name: 'javassist', version: '3.15.0-GA'
}


実際に書き換える

実際にUserという用意したクラスを書き換えて、

moneyというフィールドを追加します。


data/User.java

package data;

public class User {
private String name;

private int age;

private float height;
private float width;

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public String getUserInfo() {
return "name: " + name + ", age: " + age;
}
}



Main.java

import javassist.ClassPool;

import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;

public class Test {
public static void main(String[] args) {
try {
// 全てのクラスのプールを取得。
ClassPool pool = ClassPool.getDefault();

// パッケージ名とクラス名のフルで data.User を検索
// 見つからない場合は例外が出ます。
CtClass ctc = pool.get("data.User");

// 引数にフィールドの型、フィールド名、宣言先を指定。
CtField field = new CtField(CtClass.intType, "money", ctc);

// フィールドの修飾子を指定
// 複数の修飾子がある場合は | で追加していく。
// field.setModifiers(Modifier.STATIC | Modifier.PRIVATE);
field.setModifiers(Modifier.PRIVATE);

// フィールドを追加。
ctc.addField(field);

// 定義されているメソッドを取得。
CtMethod method = ctc.getDeclaredMethod("getUserInfo");
// メソッドの中を置き換えて money が出力されるように変更。
method.setBody("return \"name: \" + name + \", age: \" + age + \", money: \" + money;");

// クラスローダーのクラスを上書き。
Class cls = ctc.toClass(ClassLoader.getSystemClassLoader(), AsmTest.class.getProtectionDomain());

// リフレクションでクラスをインスタンスを生成。
User ins = (User) cls.newInstance();
ins.setName("Bob");// name にBobを指定。

// コンパイル後に money が追加されているのでリフレクションで取得。
Field f = cls.getDeclaredField("money");

// アクセシビリティを変更。
// private や protectedの場合にはアクセスできるように true にしてください。
f.setAccessible(true);

// moneyフィールドを int で 1500 に変更。
f.setInt(ins, 1500);

// 実際に出力。
System.out.println("name: " + ins.getName());
System.out.println("money: " + f.getInt(ins));
System.out.println(ins.getUserInfo());
} catch (Exception e) {
// エラーを出力。
System.out.println(e);
}
}
}



実行すると

結果は...

name: Bob

money: 1500
name: Bob, age: 0, money: 1500

なんと、moneyというフィールドが増えていて、

値を変更することが出来るようになっています。


まとめ

今回のフィールドの追加のみ紹介しましたが他にも様々な機能があるので

是非使って見てください。

javassist

https://github.com/jboss-javassist/javassist