LoginSignup
12
12

More than 5 years have passed since last update.

javassistで可変長引数を持つメソッドを動的に追加する

Last updated at Posted at 2014-02-17

javassistとは

javassistはバイトコードエンハンスのためのライブラリで、動的にJavaのクラスを生成させたりすることができます。生成される動的クラスはDynamic Proxyとか言われますね。フレームワークの基礎技術とも言えます。

javassist導入

pom.xmlにjavassistへの依存を追加します。実はバージョン3.13.0-GAからgroupIdがjavassistからorg.javassistに変更になってます。

xml|pom.xml
  <dependencies>
    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.18.1-GA</version>
    </dependency>
  </dependencies>

動的クラスの親となるクラスを作る

動的クラスの親にしたいクラスを用意します。メソッドは何も定義しません。

java|Target.java
package me.stormcat;
public class Target {
}

動的クラスに動的に可変長引数を持つメソッドを追加する

これはgroupIdが"javassist"時代のjavassistにはできなかった機能です。これができると黒魔術力があがります(・∀・)ニヤニヤ
では、コードを見ながら解説していきましょう。

java|Sample.java
package me.stormcat;

import java.lang.reflect.Method;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;

public class Sample {

    public static void main(String[] args) throws Exception {
        new Sample().weave();
    }

    public void weave() throws Exception {

        ClassPool pool = new ClassPool();
        pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));

        CtClass parent = pool.get("me.stormcat.Target");
        CtClass proxy = pool.makeClass("me.stormcat.Target$$Proxy", parent);

        // 追加したいメソッド、配列型で定義する
        String methodContent = 
            "public int getArgsLength(Object[] args) {" +
            "    return args.length;" +
            "}";

        CtMethod ctMethod = CtMethod.make(methodContent, proxy);
        proxy.addMethod(ctMethod);

        Class<?> proxyClass = proxy.toClass();
        Target proxyInstance = (Target) proxyClass.newInstance();

        exec(proxyInstance, "neko"); // 引数1つ
        exec(proxyInstance, "neko", "nuko"); // 引数2つ
    }

    public void exec(Target proxy,  Object ...varargs) throws Exception {
        Method method = proxy.getClass().getMethod("getArgsLength", Object[].class);
        Object result = method.invoke(proxy, new Object[]{varargs});
        System.out.println("length = " + result);
    }

}

CtClassを生成する

javassistのClassPoolから、ClassPool#getで親クラスのCtClassを取得します。今回はこれを親にして、動的にクラスを生成するのでClassPool#makeClassでTargetを親にしたProxyクラスのCtClassを生成します。

メソッド定義

methodContentという変数に文字列でメソッド定義がされています。可変長引数のメソッドを追加するので、可変長型で定義したくなりますが、javassistは解釈できずにエラーになります。そのため、Object[]型の引数にしています。

CtMethodを生成する

CtMethod#makeで、動的クラスに属するメソッドを新たに生成します。これをCtClass#addMethodで追加します。

インスタンスを生成する

CtClass#toClassすることで動的クラスのClassが生成されます(既に対象のクラスローダーに同名のクラスがロードされている場合は当然生成できないので注意)。インスタンス生成はClass#toInstanceでOk。

可変長引数でメソッド呼び出しする

execメソッドからリフレクションで呼び出して確認します。引数1つ、2つパターンで呼び出すといずれの出力になり、動的に追加した可変長メソッドの呼び出しが成功していることがわかりますね。

length = 1
length = 2
12
12
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
12
12