ASMを使ったコーディングをしていると、日本語情報の少なさに気づきます。断片的ですが自分が検索した事柄について列挙しておきます。
また公式のサンプルコードがow2のダウンロードページにzipで置いてありますので、こちらを参照すると良いでしょう。
Tips
- クラス名の内部表現を得るにはType.getInternalName(Class)
- クラスのディスクリプタの文字列表現を得るにはType.getDescriptor(Class)
- メソッドのディスクリプタ(引数と戻り値)の文字列表現を得るにはType.getMethodDescriptor(Type, Type[])
- void hoge(String)なら Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{ Type.getType(String.class) })で"(Ljava/lang/String;)V"を得られる
リテラルをスタックに積む
MethodVisitor#visitLdcInsn(Object)メソッドで積める。積める型は限られている。
MethodVisitor mv = // ... // System.err.println("TEST ASM"); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;"); mv.visitLdcInsn("TEST ASM"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
インスタンスを作る
以前のエントリで紹介したように、NEW(参照作成)→DUP(参照複製)→INVOKESPECIAL(コンストラクタ実行)の順番でバイトコードを実行する。
MethodVisitor mv = // ... // new Object(); mv.visitTypeInsn(NEW, Type.getInternalName(Object.class)); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V");
参照型の配列を作ってスタックに積む
配列の要素数をオペランドスタックに積んでからANEWARRAYを実行。NEW & INVOKESPECIAL
MethodVisitor mv = // ... // new Object[0]; mv.visitInsn(ICONST_0); mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Object.class));
メソッド開始時・終了時に処理を埋め込む
戻り値をラップする場合などメソッドの開始時・終了時に処理を埋め込む場合は、AdviceAdapterのonMethodEnter()メソッドとonMethodExit()メソッドを使うとキレイに書ける。
動的にクラスを定義する
まずはClassWriterでバイト列(≒.classファイルの中身)を作成する。
private byte[] createClassBinary() { ClassWriter cw = new ClassWriter(0); cw.visit(V1_5, ACC_PUBLIC, "pkg/OwnClass", null, "java/lang/Object", null); { // constructor MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitMaxs(2, 1); mv.visitVarInsn(ALOAD, 0); // push `this` to the operand stack mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V"); // call the constructor of super class mv.visitInsn(RETURN); mv.visitEnd(); } { // public Object get() { return new Object(); } MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "get", "()Ljava/lang/Object;", null, null); mv.visitMaxs(2, 1); mv.visitTypeInsn(NEW, Type.getInternalName(Object.class)); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V"); mv.visitInsn(ARETURN); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); }
作成したバイト列はドキュメントに載っている通りClassLoaderを自作してdefineClassを呼び出すことで、Classインスタンスに変換できる。
private static class OwnClassLoader extends ClassLoader { public Class<?> defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } } private Class<?> createClass(byte[] b) { return new OwnClassLoader().defineClass("pkg.OwnClass", b); }
なお動的にインタフェースを定義する方法は公式ドキュメントに載っている。こちらのほうが簡単。