javassistとは
javassistはバイトコードエンハンスのためのライブラリで、動的にJavaのクラスを生成させたりすることができます。生成される動的クラスはDynamic Proxyとか言われますね。フレームワークの基礎技術とも言えます。
javassist導入
pom.xmlにjavassistへの依存を追加します。実はバージョン3.13.0-GAからgroupIdがjavassistからorg.javassistに変更になってます。
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.1-GA</version>
</dependency>
</dependencies>
動的クラスの親となるクラスを作る
動的クラスの親にしたいクラスを用意します。メソッドは何も定義しません。
package me.stormcat;
public class Target {
}
動的クラスに動的に可変長引数を持つメソッドを追加する
これはgroupIdが"javassist"時代のjavassistにはできなかった機能です。これができると黒魔術力があがります(・∀・)ニヤニヤ
では、コードを見ながら解説していきましょう。
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