学习条件:学习Java ASM之前你必须对JVM指令有一定的了解,理解栈桢、操作数栈、局部变量表的概念,必须能看必要的JVM,所以建议先学习JVM指令!
一、ASM可以做什么?
简单的说Java ASM是一个非常底层的字节码操作框架,它可以创建一个全新的Java class文件也可以修改现有的class文件对象,是Java开发中非常核心的技术点。spring、Mybatis、Hibernate等现在Java中的框架底层都是用动态代理实现的核心功能,而动态代理的核心技术点就是ASM,所以说没有ASM也就没有这些框架!
二、如何使用ASM创建一个class文件对象呢?
目标:创建如下所示的一个class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.zjw;
import java.util.ArrayList;
public class HelloAsm {
private String name;
private long age;
public static long num;
public static final String ADMIN_NAME = "赵进伟";
public HelloAsm() {
}
public int method(String var1) {
System.out.println("method ing");
ArrayList var2 = new ArrayList();
StringBuilder var3 = new StringBuilder();
var3.append("Hello Java Asm StringBuilder!");
long var4 = System.nanoTime();
return 10 + 11;
}
public static void staticMethod(String var0) {
System.out.println("Hello Java Asm!");
}
}
1、创建ClassWriter对象,定义一个你所创建class的全限定名、父类、权限等
//定义一个叫做HelloAsm的类
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cw.visit(V1_8, ACC_PUBLIC, "com/zjw/HelloAsm", null, "java/lang/Object", null);
2、生成HelloAsm默认的构造方法
//生成默认的构造方法
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
//生成构造方法的字节码指令
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
3、生成成员变量
//生成成员变量
FieldVisitor fv = cw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
fv.visitEnd();
fv = cw.visitField(ACC_PRIVATE, "age", "J", null, null);
fv.visitEnd();
4、生成静成员变量
//生成静成员变量
fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "num", "J", null, 101L);
fv.visitEnd();
5、生成常量
//生成常量
fv = cw.visitField(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, "ADMIN_NAME", "Ljava/lang/String;", null, "赵进伟");
fv.visitEnd();
6、生成成员方法
//生成成员方法
mv = cw.visitMethod(ACC_PUBLIC, "method", "(Ljava/lang/String;)I", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
7、调用静态方法
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("method ing");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
8、创建局部变量
LocalVariablesSorter lvs = new LocalVariablesSorter(ACC_PUBLIC, "(Ljava/lang/String;)I", mv);
//创建ArrayList对象
//new ArrayList,分配内存但不做初始化操作
mv.visitTypeInsn(NEW, "java/util/ArrayList");
//复制栈里元素(对象地址),再次压入
mv.visitInsn(DUP);
//弹出一个对象所在地址,进行初始化操作,构造函数默认为空,此时栈大小为1
mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
int time = lvs.newLocal(Type.getType("java/util/List"));
mv.visitVarInsn(ASTORE, time);
mv.visitVarInsn(ALOAD, time);
//创建StringBuilder对象
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
time = lvs.newLocal(Type.getType("java/lang/StringBuilder"));
mv.visitVarInsn(ASTORE, time);
mv.visitVarInsn(ALOAD, time);
9、调用对象方法
//调用StringBuilder的append方法
mv.visitLdcInsn("Hello Java Asm StringBuilder!");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
10、加法运算
//调用静态方法
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
time = lvs.newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, time);
mv.visitLdcInsn(10);
mv.visitLdcInsn(11);
//加运算10+11
mv.visitInsn(IADD);
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
11、生成静态方法
//生成静态方法
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "staticMethod", "(Ljava/lang/String;)V", null, null);
//生成静态方法中的字节码指令
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello Java Asm!");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
12、获取生成的class文件对应的二进制byte数组
// 获取生成的class文件对应的二进制
byte[] code = cw.toByteArray();
13、将二进制写到本地磁盘上
//将二进制写到本地磁盘上
FileOutputStream fos = null;
try {
fos = new FileOutputStream("HelloAsm.class");
fos.write(code);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
14、直接将二进制流加载到内存中
//直接将二进制流加载到内存中 name可以传入null
/*注意:defineClass是ClassLoader的protected方法<本类、子类、同包类中才可以调用>,所以自己想办法吧*/
Class<?> clazz=defineClass("com.zjw.HelloAsm", code, 0, code.length);
到此ASM创建一个class就完成了!
作者整理资料也是查阅了N篇文章,对你有帮助请点个赞吧!
需要作者项目源码欢迎在评论区留言!