LoginSignup
5
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-23

はじめに

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

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3