ASM(三) 利用Method组件动态生成方法的字节码
2015-07-01 21:48
525 查看
一、概述
ASM的CoreApi中还提供了对class
中方法的生成和解析的组件。前面两篇着重介绍了ClassVisitor组件的应用场景。ClassVisitor Api
中的visitMethod(int access, String name, String desc, String signature, String[] exceptions)方法返回了一个MethodVisitor对象,MethodVisitor类提供了对于字节码文件中方法的字节码进行解析。同ClassVisitor一样,MethodVisitor的实例方法也需要按照一定的顺序调用。
对于动态生成方法的实现,ASM提供的主要接口就是MethodVisitor。Methods组件主要涉及到一下三个类:
1、ClassReader类负责解析字节码中的method部分,并且顺序调用MethodVisitor的方法。其中MethodVisitor是通过ClassVisitor的visitMethod返回的对象。而ClassVisitor是通过accept方法的参数形式转递给ClassReader。accept方法在前面章介绍ASM的core
api的ClassVisitor部分已经介绍过了。
2、ClassWriter的visitMethod方法返回了继承自MethodVisitor的实例。并且以二进制数组的形式构建了编译后的方法。
3、MethodVisitor类似一个事件过滤器,可以接受另一个MethodVisitor实例,代理调用所有这个MethodVisitor实例的方法。这个也是类似于前面两章介绍的ClassVisitor。
下面就开始介绍下用ASM
的MethodVisitor如何生成和变更、转移methods字节码。
二、生成字节码methods
在了解ASM的methods api之前,必须要清楚一些字节码的指令和JVM
执行引擎在运行时数据区是运用字节码指令解释执行的。这里可以参看http://yunshen0909.iteye.com/blog/2220937以及http://yunshen0909.iteye.com/blog/2221144。这里我们结合《JVM字节码指令对于栈帧数据操作举例》中的例子。生成如下方法:
为了方便查看字节码生成效果,我们通过ClassWriter来输出字节数组,并写到文件中,方便查看class文件。对于ClassWriter
Api参考http://yunshen0909.iteye.com/blog/2219540。Java代码如下:
这里先说明一下栈图(Stack
Map Table)的概念。详见: http://yunshen0909.iteye.com/admin/blogs/2222417
在Java7版本之后,需要强制实现栈图结构,不过还好ASM框架给我们处理了生成字节码栈图的细节。构建栈图在ASM框架中,可以通过在无条件跳转语句后调用mv.visitFrame();方法或者调整ClassWriter()构造器的参数方式来实现,ClassWriter
cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);,构造器中的COMPUTE_FRAMES参数会为我们计算Stack Map Table中的frame。本例中,我们采用“手动挡”。visitFrame(type,
nLocal, local, nStack, stack)的第一个参数是stack map frame的操作类型,nLocal和nStack是局部变量和操作数栈的size。Local和stack是包含相关类型的数组。本例中,只有两个frame,且记录的状态中操作数栈都是空的,所以参数是(Opcodes.F_SAME,
0, null, 0, null)。
这里再额外说明一下ClassWriter构造器的另外一种参数。如果我们使用了ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);那么COMPUTE_MAXS就是替我们计算局部变量表和操作数栈的大小,这时候我们还是需要调用
mv.visitMaxs(2, 2);方法,但是可以传递任意参数,因为COMPUTE_MAXS的方式会帮助我们重新计算局部变量和操作数的size。还需要注意的是COMPUTE_MAXS不会为我们计算StackMapFrame,而COMPUTE_FRAMES既会计算栈size,也会计算StackMapFrame,当然使用COMPUTE_MAXS会让ClassWriter慢10%,而使用COMPUTE_FRAMES会慢20%(数据来自ASM官方说明文档,这里就不再测试)。当然,我觉得,如果使用起来的话,我还是宁可用自动挡。这样可以适当增强代码可读性,减少了代码维护成本。如果有很好的算法(如果你确定你比ASM框架写出了更好的实现算法)来计算这些参数,当然手动挡也是一种乐趣。
上述实现中我们看到了Label label = new Label()这个语句中,label的作用是为了条件跳转,其实也可以理解成字节码指令的参数。所以label必须对应一条字节码指令,通过visitLabel(label)来调用,并且visitLabel的调用必须紧跟随着label对象指定的指令。如例子中,第一个label指向goto后,所以顺序必须是:mv.visitJumpInsn(Opcodes.GOTO,
end);
mv.visitLabel(label);,第二个label也是同理。但是多个字节码指令可能指向同一个label,比如同时跳转到某一个指令执行的情况,并且label是方法独有的,label对象不能跨方法调用。
下面我们再看一下,例中方法生成的字节码信息。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202010/16/4a7c9794a7d106af37c9e37982f3b64d.png)
好了,我好饿,先写到这里,后续会继续介绍Method
Api 的另外一种用法,其实猜猜也知道,同ClassVisitor一样,不仅可以生成字节码,当然也可以动态改变、添加、删除方法的字节码。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202010/16/27475a2daf7802577cb8de78e59be875.png)
大小: 33.4 KB
查看图片附件
ASM的CoreApi中还提供了对class
中方法的生成和解析的组件。前面两篇着重介绍了ClassVisitor组件的应用场景。ClassVisitor Api
中的visitMethod(int access, String name, String desc, String signature, String[] exceptions)方法返回了一个MethodVisitor对象,MethodVisitor类提供了对于字节码文件中方法的字节码进行解析。同ClassVisitor一样,MethodVisitor的实例方法也需要按照一定的顺序调用。
visitAnnotationDefault? ( visitAnnotation |visitParameterAnnotation |visitAttribute )* ( visitCode ( visitTryCatchBlock |visitLabel |visitFrame |visitXxxInsn| visitLocalVariable| visitLineNumber)* visitMaxs)? visitEnd |
1、ClassReader类负责解析字节码中的method部分,并且顺序调用MethodVisitor的方法。其中MethodVisitor是通过ClassVisitor的visitMethod返回的对象。而ClassVisitor是通过accept方法的参数形式转递给ClassReader。accept方法在前面章介绍ASM的core
api的ClassVisitor部分已经介绍过了。
2、ClassWriter的visitMethod方法返回了继承自MethodVisitor的实例。并且以二进制数组的形式构建了编译后的方法。
3、MethodVisitor类似一个事件过滤器,可以接受另一个MethodVisitor实例,代理调用所有这个MethodVisitor实例的方法。这个也是类似于前面两章介绍的ClassVisitor。
下面就开始介绍下用ASM
的MethodVisitor如何生成和变更、转移methods字节码。
二、生成字节码methods
在了解ASM的methods api之前,必须要清楚一些字节码的指令和JVM
执行引擎在运行时数据区是运用字节码指令解释执行的。这里可以参看http://yunshen0909.iteye.com/blog/2220937以及http://yunshen0909.iteye.com/blog/2221144。这里我们结合《JVM字节码指令对于栈帧数据操作举例》中的例子。生成如下方法:
public void addEspresso(int espresso) { if (espresso > 1) { this.espresso = espresso; } else { throw new IllegalArgumentException(); } }
为了方便查看字节码生成效果,我们通过ClassWriter来输出字节数组,并写到文件中,方便查看class文件。对于ClassWriter
Api参考http://yunshen0909.iteye.com/blog/2219540。Java代码如下:
package asm.core.asm.core.method; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /** * methods api 动态生成字节码 Created by yunshen.ljy on 2015/6/24. */ public class GenerateClasses { public static void main(String[] args) throws IOException { ClassWriter cw = new ClassWriter(0); cw.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "bytecode/MethodGenClass", null, "java/lang/Object", null); cw.visitField(Opcodes.ACC_PRIVATE, "espresso", "I", null, null).visitEnd(); MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "addEspresso", "(I)V", null, null); // 方法访问开始 mv.visitCode(); mv.visitVarInsn(Opcodes.ILOAD, 1); // label 代表跳转的字节码位置。 Label label = new Label(); mv.visitJumpInsn(Opcodes.IFLT, label); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ILOAD, 1); mv.visitFieldInsn(Opcodes.PUTFIELD, "bytecode/MethodGenClass", "espresso", "I"); Label end = new Label(); mv.visitJumpInsn(Opcodes.GOTO, end); mv.visitLabel(label); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); // 创建Exception对象指令 mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalArgumentException"); mv.visitInsn(Opcodes.DUP); // 调用方法指令 mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "()V", false); mv.visitInsn(Opcodes.ATHROW); mv.visitLabel(end); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(2, 2); // 方法访问结束 mv.visitEnd(); cw.visitEnd(); byte[] b = cw.toByteArray(); File file = new File("MethodGenClass.class"); FileOutputStream fout = new FileOutputStream(file); fout.write(b); fout.close(); } }
这里先说明一下栈图(Stack
Map Table)的概念。详见: http://yunshen0909.iteye.com/admin/blogs/2222417
在Java7版本之后,需要强制实现栈图结构,不过还好ASM框架给我们处理了生成字节码栈图的细节。构建栈图在ASM框架中,可以通过在无条件跳转语句后调用mv.visitFrame();方法或者调整ClassWriter()构造器的参数方式来实现,ClassWriter
cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);,构造器中的COMPUTE_FRAMES参数会为我们计算Stack Map Table中的frame。本例中,我们采用“手动挡”。visitFrame(type,
nLocal, local, nStack, stack)的第一个参数是stack map frame的操作类型,nLocal和nStack是局部变量和操作数栈的size。Local和stack是包含相关类型的数组。本例中,只有两个frame,且记录的状态中操作数栈都是空的,所以参数是(Opcodes.F_SAME,
0, null, 0, null)。
这里再额外说明一下ClassWriter构造器的另外一种参数。如果我们使用了ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);那么COMPUTE_MAXS就是替我们计算局部变量表和操作数栈的大小,这时候我们还是需要调用
mv.visitMaxs(2, 2);方法,但是可以传递任意参数,因为COMPUTE_MAXS的方式会帮助我们重新计算局部变量和操作数的size。还需要注意的是COMPUTE_MAXS不会为我们计算StackMapFrame,而COMPUTE_FRAMES既会计算栈size,也会计算StackMapFrame,当然使用COMPUTE_MAXS会让ClassWriter慢10%,而使用COMPUTE_FRAMES会慢20%(数据来自ASM官方说明文档,这里就不再测试)。当然,我觉得,如果使用起来的话,我还是宁可用自动挡。这样可以适当增强代码可读性,减少了代码维护成本。如果有很好的算法(如果你确定你比ASM框架写出了更好的实现算法)来计算这些参数,当然手动挡也是一种乐趣。
上述实现中我们看到了Label label = new Label()这个语句中,label的作用是为了条件跳转,其实也可以理解成字节码指令的参数。所以label必须对应一条字节码指令,通过visitLabel(label)来调用,并且visitLabel的调用必须紧跟随着label对象指定的指令。如例子中,第一个label指向goto后,所以顺序必须是:mv.visitJumpInsn(Opcodes.GOTO,
end);
mv.visitLabel(label);,第二个label也是同理。但是多个字节码指令可能指向同一个label,比如同时跳转到某一个指令执行的情况,并且label是方法独有的,label对象不能跨方法调用。
下面我们再看一下,例中方法生成的字节码信息。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202010/16/4a7c9794a7d106af37c9e37982f3b64d.png)
好了,我好饿,先写到这里,后续会继续介绍Method
Api 的另外一种用法,其实猜猜也知道,同ClassVisitor一样,不仅可以生成字节码,当然也可以动态改变、添加、删除方法的字节码。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202010/16/27475a2daf7802577cb8de78e59be875.png)
大小: 33.4 KB
查看图片附件
相关文章推荐
- JVM StackMapTable 属性的作用及理解
- Java 并发包之线程池和原子计数
- Flex&iBatis&Hibernate&Spring—师徒奶茶系统V1总结
- websocket客户端
- hdu4521 小明系列的问题——小明序列(LIS变种 (段树+单点更新解决方案))
- Linux命令-终止进程命令:pkill
- Java知多少(109)数据库更新
- <s:if 标签变量比较
- iOS开发ARC内存管理技术要点
- 寒假的唠叨
- iBATIS&Spring合奏(五)--整合lucene搜索表字段内容
- iBATIS&Spring合奏(四)--设计模式in iBATIS
- iBATIS&Spring合奏(三)--事务&动态SQL
- iBATIS&Spring合奏(二)--Flex前端融合
- iBATIS&Spring合奏(一)--DAO
- Spring AOP 织入初探--通过架构看实现
- 大学这几年
- 读《SaaS架构设计》一书有感
- 蛋糕求职记--小时候淘气,长大了淘宝
- [莫比乌斯函数]BZOJ 2440 完全平方数