asm學習筆記之生成方法

MethodVisitor 簡介

如果要生成方法實現的字節碼,就要藉助MethodVisitor類了,可以通過ClassVisitor的visitMethod方法得到一個MethodVisitor子類TraceMethodVisitor的實例。以下是MethodVisitor API裏面的visitXxx方法:

AnnotationVisitor visitAnnotationDefault();
AnnotationVisitor visitAnnotation(String desc, boolean visible);
AnnotationVisitor visitParameterAnnotation(int parameter,String desc, boolean visible);
void visitAttribute(Attribute attr);
void visitCode();
void visitFrame(int type, int nLocal, Object[] local, int nStack,Object[] stack);
void visitInsn(int opcode);
void visitIntInsn(int opcode, int operand);
void visitVarInsn(int opcode, int var);
void visitTypeInsn(int opcode, String desc);
void visitFieldInsn(int opc, String owner, String name, String desc);
void visitMethodInsn(int opc, String owner, String name, String desc);
void visitInvokeDynamicInsn(String name, String desc, Handle bsm,Object... bsmArgs);
void visitJumpInsn(int opcode, Label label);
void visitLabel(Label label);
void visitLdcInsn(Object cst);
void visitIincInsn(int var, int increment);
void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels);
void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels);
void visitMultiANewArrayInsn(String desc, int dims);
void visitTryCatchBlock(Label start, Label end, Label handler,String type);
void visitLocalVariable(String name, String desc, String signature,Label start, Label end, int index);
void visitLineNumber(int line, Label start);
void visitMaxs(int maxStack, int maxLocals);
void visitEnd();
和ClassVisitor一樣,調用MethodVisitor的方法也必須遵循以下順序:

visitAnnotationDefault?
( visitAnnotation | visitParameterAnnotation | visitAttribute )*
( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |
visitLocalVariable | visitLineNumber )*
visitMaxs )?
visitEnd

說明:

1、如果要生成方法的代碼,需要先以visitCode開頭,訪問結束需要調用visitEnd方法;

2、方法都是在線程中執行的,每個線程有自己的虛擬機棧,這個棧與線程同時創建,用於存儲棧幀。棧幀隨着方法調用而創建,方法結束時銷燬。每個棧幀都有自己的本地變量表、操作數棧和指向常量池的引用。

本地變量表和操作數棧的大小在編譯期確定,在asm中可以通過visitMaxs來指定本地變量表與操作數棧的大小。visitFrame方法可以指定棧幀中的本地變量與操作數。

對本地變量和操作數棧的大小設置受ClassWriter的flag取值影響:

(1)new ClassWriter(0),表明需要手動計算棧幀大小、本地變量和操作數棧的大小;

(2)new ClassWriter(ClassWriter.COMPUTE_MAXS)需要自己計算棧幀大小,但本地變量與操作數已自動計算好,當然也可以調用visitMaxs方法,只不過不起作用,參數會被忽略;

(3)new ClassWriter(ClassWriter.COMPUTE_FRAMES)棧幀本地變量和操作數棧都自動計算,不需要調用visitFrame和visitMaxs方法,即使調用也會被忽略。

這些選項非常方便,但會有一定的開銷,使用COMPUTE_MAXS會慢10%,使用COMPUTE_FRAMES會慢2倍。

3、visitInsn、visitVarInsn、visitMethodInsn等以Insn結尾的方法可以添加方法實現的字節碼。


實例

下面用ClassMethod來生成add方法的實現代碼,並調用:

假設有如下接口:

package asm.demo;

public interface AddOper extends Oper {
    public static final String SYMBOL = "+";

    public int add(int a, int b);
}
要生成如下java代碼:

package asm.demo;

public class AddOperImpl implements AddOper {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}
對應的生成方法如下:

public static void main(String[] args) throws Exception {
	ClassWriter cw = new ClassWriter(0);
	PrintWriter printWriter = new PrintWriter(System.out);
	TraceClassVisitor visitor = new TraceClassVisitor(cw, printWriter);

	visitor.visit(V1_5, ACC_PUBLIC, "asm/demo/AddOperImpl", null, "java/lang/Object", new String[]{"asm/demo/AddOper"});

	//添加構造方法
	MethodVisitor mv = visitor.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
	mv.visitCode();
	mv.visitVarInsn(ALOAD, 0);
	mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
	mv.visitInsn(RETURN);
	mv.visitMaxs(1, 1);
	mv.visitEnd();

	// 添加add方法
	mv = visitor.visitMethod(ACC_PUBLIC, "add", "(II)I", null, null);
	mv.visitCode();
	mv.visitVarInsn(ILOAD, 1);
	mv.visitVarInsn(ILOAD, 2);
	mv.visitInsn(IADD);
	mv.visitInsn(IRETURN);
	mv.visitMaxs(2, 3);
	mv.visitEnd();

	visitor.visitEnd();

	FileOutputStream fos = new FileOutputStream(new File("D:/code/asmdemo/out/production/asmdemo/asm/demo/AddOperImpl.class"));
	fos.write(cw.toByteArray());
	fos.close();
}
說明:

(1)這裏生成了兩個方法,分別是構造方法<init>和add方法

(2)構造方法的本地方法與操作數棧大小分別爲1,是因爲aload_0指令表示從局部變量表加載一個reference類型值到操作數棧,這裏局部變量表只有this引用;

(3)add方法的操作數棧大小爲2,局部變量表大小爲3,分別爲this,a,b。

用javap -c AddOperImpl.class命令查看字節碼如下:

public class asm.demo.AddOperImpl implements asm.demo.AddOper {
  public asm.demo.AddOperImpl();
    Code:
       0: aload_0
       1: invokespecial #10                 // Method java/lang/Object."<init>":()V
       4: return

  public int add(int, int);
    Code:
       0: iload_1
       1: iload_2
       2: iadd
       3: ireturn
}

調用add方法結果如預期:

MyClassLoader classLoader = new MyClassLoader();
Class<?> clazz = classLoader.defineClass("asm.demo.AddOperImpl", cw.toByteArray());
Method addMethod = clazz.getMethod("add", int.class, int.class);
Object result = addMethod.invoke(clazz.newInstance(), 10, 20);
if(result != null && result instanceof Integer)
System.out.println((Integer) result);


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章