asm学习笔记之生成方法
2015-10-07 14:52
495 查看
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);
相关文章推荐
- 条款22:将成员变量声明为private
- android 签名、混淆打包
- 关于自己的学习oc的学习笔记 - NSCalendar
- 第二次实验内容
- strip, 关于去除目标文件种的不必要信息
- UVA 1152 4 Values whose Sum is 0
- 惠普实训第一阶段学习总结
- Jekyll & Github Pages
- 11.14 MATLAB编译错误中英对照29
- shell文字过滤程序(十):cut命令
- CF 584B Kolya and Tanya
- codeforces 584E Anton and Ira [想法题]
- Servlet基本的传递参数的设置
- Linux GDB 常用命令
- 字符串q次操作将(l,r)内的字符升序或降序排列 计数排序 + 线段树优化 Codeforces div2 558E A Simple Task
- 11.13 matlab编译生成执行文件注意的若干问题
- Android中AsyncTask用法
- 全栈开发教学学习系列1——前言
- 当幸福来敲门
- 信号量线程控制