写在前面
源码 。
看下如何使用ASM来写如下的类:
package com.dahuyou.demo.asm;public class AsmSumOfTwo {public AsmSumOfTwo() {}public static void main(String[] var0) {int var1 = (new AsmSumOfTwo()).sum(1, 2);System.out.println(var1);}public int sum(int var1, int var2) {return var1 + var2;}
}
1:编码
源码:
package com.dahuyou.asm.helloworld;import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;public class SumOfTwo extends ClassLoader {public static void main(String[] args) throws Exception {// 生成二进制字节码byte[] bytes = generate();// 输出字节码outputClazz(bytes);// 加载AsmHelloWorldClass<?> clazz = new SumOfTwo().defineClass("com.dahuyou.demo.asm.AsmSumOfTwo", bytes, 0, bytes.length);// 反射获取 main 方法Method main = clazz.getMethod("main", String[].class);// 调用 main 方法main.invoke(null, new Object[]{new String[]{}});}private static byte[] generate() {ClassWriter classWriter = new ClassWriter(0);{MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);mv.visitCode();// aload指令,加载本地变量表0位置对象(a)mv.visitVarInsn(Opcodes.ALOAD, 0);// invokespecial指令,调用方法<init>mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");// return指令,返回voidmv.visitInsn(Opcodes.RETURN);// 操作数栈,局部变量表大小mv.visitMaxs(1, 1);mv.visitEnd();}// 定义对象头;版本号、修饰符、全类名、签名、父类、实现的接口classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "com/dahuyou/demo/asm/AsmSumOfTwo", null, "java/lang/Object", null);// 添加方法;修饰符、方法名、描述符、签名、异常MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);// int var1 = (new AsmSumOfTwo()).sum(1, 2);中的 new AsmSumOfTwo(),即创建实例对象,并压入操作数栈栈顶methodVisitor.visitTypeInsn(Opcodes.NEW, "com/dahuyou/demo/asm/AsmSumOfTwo");// 获取操作数栈栈顶元素,并复制,重新压回,此时操作数栈栈顶有连续的两个new AsmSumOfTwo()引用methodVisitor.visitInsn(Opcodes.DUP);// 栈顶元素出栈,并调用初始化方法<init>,即默认的空构造函数methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "com/dahuyou/demo/asm/AsmSumOfTwo", "<init>", "()V");// iconst 加载整数常量 1,2,并压到栈顶,此时,操作数栈元素为2,1,AsmSumOfTwo实例引用methodVisitor.visitInsn(Opcodes.ICONST_1);methodVisitor.visitInsn(Opcodes.ICONST_2);// int var1 = (new AsmSumOfTwo()).sum(1, 2);中的.sum(1, 2) (II)I入参两个int,返回int,函数的结果存储栈顶methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/dahuyou/demo/asm/AsmSumOfTwo", "sum", "(II)I");// 将栈顶元素存储到本地变量表 slot 1位置,即赋值给var1(实际程序运行过程中jvm是不关心变量名称的,因为完全不影响运行结果)methodVisitor.visitVarInsn(Opcodes.ISTORE, 1);// 获取静态属性System.outmethodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");// 将本地变量表中slot 1位置的int变量推动到操作数栈栈顶(因为要做sout)methodVisitor.visitVarInsn(Opcodes.ILOAD, 1);// 获取栈顶元素,并作sout,这样int var1 = (new AsmSumOfTwo()).sum(1, 2);的var1就完成打印了// (I)V参数int,返回voidmethodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V");// 返回methodVisitor.visitInsn(Opcodes.RETURN);// 设置操作数栈的深度和局部变量的大小methodVisitor.visitMaxs(3, 2);// 方法结束methodVisitor.visitEnd();/*** 以下代码定义如下的函数:* public int sum(int i, int m) {* return i + m;* }*/MethodVisitor methodVisitor_sum = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sum", "(II)I", null, null);// 加载本地变量表slot 1,2位置int变量到操作数栈,并调用add指令执行加法methodVisitor_sum.visitVarInsn(Opcodes.ILOAD, 1);methodVisitor_sum.visitVarInsn(Opcodes.ILOAD, 2);methodVisitor_sum.visitInsn(Opcodes.IADD);// 返回methodVisitor_sum.visitInsn(Opcodes.IRETURN);// 设置操作数栈的深度和局部变量的大小methodVisitor_sum.visitMaxs(2, 3);methodVisitor_sum.visitEnd();// 类完成classWriter.visitEnd();// 生成字节数组return classWriter.toByteArray();}private static void outputClazz(byte[] bytes) {// 输出类字节码FileOutputStream out = null;try {out = new FileOutputStream("AsmSumOfTwo.class");System.out.println("ASM类输出路径:" + (new File("")).getAbsolutePath());out.write(bytes);} catch (Exception e) {e.printStackTrace();} finally {if (null != out) try {out.close();} catch (IOException e) {e.printStackTrace();}}}}
写了非常详细的注释,看下吧,还有哪里不懂的可以留言告知。然后运行下:
反编译看对应的Java代码:
写在后面
一个多么简单的加法函数,通过底层字节码方式来编写还是挺麻烦的,只能说底层还是很复杂的,但是想要进阶,不了解底层又是不行的。所以,在这winter already coming的行业环境下,加油吧!!!
参考文章列表
JVM 虚拟机字节码指令表 。