JAVA字节码框架ASM生成类的简单应用
一、ASM框架的简介
ASM 是一个通用的 Java 字节码操作和分析框架。一般用来动态生成类或者增强既有类的功能。也就是既可以创建class文件,也可以修改class文件。
ASM官网:https://asm.ow2.io/index.html
简单描述就是一个操作java字节码的框架
众所周知,java的执行过程是将java文件通过javac编译成为class字节码文件,但是字节码文件又是一个晦涩难懂的二进制文件,虽然有相应的规范,但是理解起来还是非常的繁琐,不容易去记忆。
>(图片来自网络,侵权必删)
二、代码实现
前言
本篇博客仅是简单通过ASM框架生成一个类,并且使用这个类输出HelloWorld!
,仅作新手了解认识ASM框架的启蒙,并不做具体内容的讲解。
HelloWorldByAsm.java
我们先放整个代码,然后再对代码内容进行分析
首先在maven里面导入依赖
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
</dependency>
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.lang.reflect.Method;
public class HelloWorldByAsm {
public static void main(String[] args) throws Exception {
// 生成二进制字节码
byte[] bytes = MyClassLoader.generate();
// 使用自定义的ClassLoader
MyClassLoader cl = new MyClassLoader();
// 加载我们生成的 HelloWorld 类
Class<?> clazz = cl.defineClass("com.kevinwalker.asm.HelloWorld", bytes);
// 反射获取 main 方法
Method main = clazz.getMethod("main", String[].class);
// 调用 main 方法
main.invoke(null, new Object[]{new String[]{}});
}
}
/**
* 自定义ClassLoader以支持加载字节数组形式的字节码
*/
class MyClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] b) {
// ClassLoader是个抽象类,而ClassLoader.defineClass 方法是protected的
// 所以我们需要定义一个子类将这个方法暴露出来
return super.defineClass(name, b, 0, b.length);
}
static byte[] generate() {
ClassWriter cw = new ClassWriter(0);
// 定义对象头:版本号、修饰符、全类名、签名、父类、实现的接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/kevinwalker/asm/HelloWorld",
null, "java/lang/Object", null);
// 添加方法:修饰符、方法名、描述符、签名、抛出的异常
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main",
"([Ljava/lang/String;)V", null, null);
// 执行指令:获取静态属性
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// 加载常量 load constant
mv.visitLdcInsn("HelloWorld!");
// 调用方法
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
// 返回
mv.visitInsn(Opcodes.RETURN);
// 设置栈大小和局部变量表大小
mv.visitMaxs(2, 1);
// 方法结束
mv.visitEnd();
// 类完成
cw.visitEnd();
// 生成字节数组
return cw.toByteArray();
}
}
下面我们直接运行该类,得到如下结果:
可以看到输出HelloWorld!
了。
HelloWorldByAsm类
如果仔细阅读过代码可以看到main函数部门并没有写输出的语句,而实际的情况是通过我们定义的MyClassLoader
类去重写defineClass
方法从而通过bytes去实例出来我们所要生成的HelloWorld
类,然后通过反射去调用HelloWorld
类的main函数,整个过程中我们并没有去写这个HelloWorld
类,而是通过generate
方法生成了这个类的bytes。
generate方法
第一步:我们告诉ASM我们想要开始编写一个新类的方式。我们让ClassWriter为我们生成这些类的字节数据。
第二步:在visit
方法内,一定要引入Java8,因为需要jre>=8才能运行,后面设置访问修饰符,类名地址等参数。
第三步:通过MethodVisitor
来描述main方法,并且通过([Ljava/lang/String;)V
描述参数及返回值。
第四步:获取静态属性out。(也就是System.out)
第五步:加载常量’HelloWorld!‘。
第六步:调用静态属性out的println方法输出’HelloWorld!’。
第七步:返回,设置栈大小和局部变量大小。
第八步:返回cw.toByteArray(),ClassWriter
帮我们生成的类的字节参数。
最后补充
再次说明,本教程仅作为对ASM简单应用的介绍,并不是一篇完整的教程,笔者尽可能的 通过简洁的语言和代码来展示ASM的强大能力,当然ASM远不止本篇介绍的生成类的功能。希望本篇教程可以给你带来对asm简单的体验和启蒙。下面附上部分资料:
关于第三步的
([Ljava/lang/String;)V
想了解的可以参考:https://blog.csdn.net/longaiyunlay/article/details/80049440
再补充一个ASM框架的javadoc:https://asm.ow2.io/javadoc/index.html