写在前面
源码 。
看下如何使用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);
// 加载AsmHelloWorld
Class<?> 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指令,返回void
mv.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.out
methodVisitor.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,返回void
methodVisitor.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 虚拟机字节码指令表 。