在java语言中,Java文件在编译时会将java文件编译成.class的字节码文件。通常我们在写代码时只涉及到java文件开发,但有时候在一些特殊的场景下,比如在代码中统一插入一些性能监控的代码,我们可以利用像字节码插桩这样的动态修改class文件的技术来实现。动态修改生成class文件也是AOP编程的基础。我们还是有必要去了解一下的。下面介绍一下如何利用ASM来实现动态生成class文件。
首先在项目用引入ASM的test依赖包,注意这个依赖包只能在test目录下使用。
testImplementation 'org.ow2.asm:asm:7.1'
testImplementation 'org.ow2.asm:asm-commons:7.1'
接着我们来写一段要实现出来的代码:
public class InjectTest {
public InjectTest() {
}
@ASMTest
public static void main(String[] args) throws InterruptedException {
// 通过ASM动态添加
long var1 = System.currentTimeMillis();
Thread.sleep(3000);
// 通过ASM动态添加
long var3 = System.currentTimeMillis();
// 通过ASM动态添加
System.out.println("时间是:" + (var3 - var1));
}
void method() {}
}
上面这段代码中,假如我们的项目中有一段耗时的代码,这里用Thread.sleep(3000)模拟,我们需要去计算这段代码执行到底耗费了多长时间,然后将其打印出来,那么利用ASM应该怎么做呢。
@Test
public void test() {
try {
// 获取已经生成的class文件
FileInputStream fis = new FileInputStream("/Users/iosdev/Desktop/Demo/ByteCodeInstDemo2/app/src/test/java/com/example/bytecodeinstdemo2/InjectTest.class");
// 获取一个分析器, 去读class文件
ClassReader classReader = new ClassReader(fis);
// 获取一个分析器, 去写class文件
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// 开始插桩, 用最新版本ASM7,
classReader.accept(new MyClassVisitor(Opcodes.ASM7, classWriter), ClassReader.EXPAND_FRAMES);
// 将写完的内容转换成byte数组
byte[] bytes = classWriter.toByteArray();
// 将修改后的class文件写入(路径是绝对路径)
FileOutputStream fos = new FileOutputStream("/Users/iosdev/Desktop/Demo/ByteCodeInstDemo2/app/src/test/java/com/example/bytecodeinstdemo2/InjectTest2.class");
// 将bytes写到文件中
fos.write(bytes);
fos.close();
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 用来访问类信息的
*/
static class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(int api) {
super(api);
}
public MyClassVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
// 读取class文件信息的时候, 每读到一个方法, 就执行下面的代码一次
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
return new MyMethodVisitor(api, methodVisitor, access, name, descriptor);
}
}
/**
* 这里用来访问方法的
*/
static class MyMethodVisitor extends AdviceAdapter {
protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}
// 局部空间表的下标
int s;
/**
* 方法进入的时候执行
*/
@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (inJect == false) {
return;
}
invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
// 保存数据类型为long类型
s = newLocal(Type.LONG_TYPE);
// 存放在布局变量表下标为1的位置
storeLocal(1);
}
// 局部空间表的下标
int e;
/**
* 方法退出的时候执行
* @param opcode
*/
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
if (inJect == false) {
return;
}
invokeStatic(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
e = newLocal(Type.LONG_TYPE);
storeLocal(e);
getStatic(Type.getType("Ljava/lang/System;"), "out", Type.getType("Ljava/io/PrintStream;"));
// L3
// LINENUMBER 13 L3
// GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
//分配内存 并dup压入栈顶让下面的INVOKESPECIAL 知道执行谁的构造方法创建StringBuilder
newInstance(Type.getType("Ljava/lang/StringBuilder;"));
// NEW java/lang/StringBuilder
dup();
// DUP
// visitLdcInsn("");
// LDC "\u65f6\u95f4\u662f:"
invokeConstructor(Type.getType("Ljava/lang/StringBuilder.<init>"), new Method("Ljava/lang/String;", "V"));
// INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
loadLocal(3);
// LLOAD 3
// LLOAD 1
// LSUB
// INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
// INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
// INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
// L4
// LINENUMBER 14 L4
// RETURN
visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
}
boolean inJect = false;
/**
* 如果一个方法上有注解, 就执行该方法
* @param descriptor
* @param visible
* @return
*/
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
System.out.println(getName() + "->" + descriptor);
// 这里表示, 如果注解信息中带有ASMTest,则inJect为true, 上面的方法中我们控制了只有inJect为true时才插入class字节码
if ("Lcom/example/bytecodeinstdemo2/ASMTest;".equals(descriptor)) {
inJect = true;
}
return super.visitAnnotation(descriptor, visible);
}
}
这里完成的一个功能是在原有的代码中, 插入一段计时的代码, 用来计算代码执行的时间。当然我觉得对于程序员来说,要自己去写一些字节码的调用方法是非常繁琐且耗费时间的。所以有没有什么办法能够更简洁更友好一点来帮助我们生成我们想要的class文件呢。当然是有的~首先我们得下载ASM插件,
ASM Bytecode Viewer插件.png
image.png
ASM Bytecode视图.png
ASMified视图.png
public class HelloWorld2 implements Opcodes {
public static final String PATH = "app/src/test/java/com/example/bytecodeinstdemo2/";
public static void main(String[] args) {
// 获取一个分析器, 去写class文件
ClassWriter classWriter = new ClassWriter(0);
// 变量的访问者,用来访问class文件中的变量
FieldVisitor fieldVisitor;
// 方法的访问者,用来访问class文件中的方法
MethodVisitor methodVisitor;
// 注解的访问者,用来访问class文件中的注解信息
AnnotationVisitor annotationVisitor0;
// 定义一个叫做Example的类
// V1_1:生成的class的版本号
// ACC_PUBLIC:表示该类的访问标识, 是一个public类
// Example:生成的类的类名,这里是全限定名,如果有包名,则需要传入 包名+类名
// signature:泛型相关的
// superName:当前类的父类的全限定名
// interfaces:传入当前要生成的类的直接实现的接口
classWriter.visit(V1_7, ACC_PUBLIC | ACC_SUPER, "com/example/bytecodeinstdemo2/ASMDemo", null, "java/lang/Object", null);
classWriter.visitSource("HelloWorld2.java", null);
{
// 定义一个方法, 这里表明是生成一个构造方法
// ACC_PUBLIC: 方法的访问权限是public
// <init>: 方法的方法名, 构造方法方法名是<init>
// ()V: 方法描述符, 构造方法无参数,无返回值,描述符为()V
// signature: 泛型相关
// exceptions: 方法申明可能抛出的异常
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
// 读取源文件中代码的行数
methodVisitor.visitLineNumber(5, label0);
// 生成构造方法的字节码指令, 将第0个本地变量(也就是this)压入操作数栈。
methodVisitor.visitVarInsn(ALOAD, 0);
// 调用visitMethodInsn方法, 生成invokespecial指令, 调用父类(也就是Object)的构造方法。
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(6, label1);
// 调用visitInsn方法,生成return指令, 方法返回。
methodVisitor.visitInsn(RETURN);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLocalVariable("this", "Lcom/example/bytecodeinstdemo2/ASMDemo;", null, label0, label2, 0);
// 调用visitMaxs方法, 指定当前要生成的方法的最大局部变量和最大操作数栈。 对应Code属性中的max_stack和max_locals 。
methodVisitor.visitMaxs(1, 1);
// 调用visitEnd方法, 表示当前要生成的构造方法已经创建完成。
methodVisitor.visitEnd();
}
{
// 生成main方法
methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, new String[]{"java/lang/InterruptedException"});
{
annotationVisitor0 = methodVisitor.visitAnnotation("Lcom/example/bytecodeinstdemo2/ASMDemo;", false);
annotationVisitor0.visitEnd();
}
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(10, label0);
// long var1 = System.currentTimeMillis();
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
methodVisitor.visitVarInsn(LSTORE, 1);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(11, label1);
methodVisitor.visitLdcInsn(new Long(3000L));
// Thread.sleep(3000);
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(12, label2);
// long var3 = System.currentTimeMillis();
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
methodVisitor.visitVarInsn(LSTORE, 3);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(13, label3);
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
methodVisitor.visitInsn(DUP);
// System.out.println("时间是:" + (var3 - var1));
methodVisitor.visitLdcInsn("\u65f6\u95f4\u662f:");
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false);
methodVisitor.visitVarInsn(LLOAD, 3);
methodVisitor.visitVarInsn(LLOAD, 1);
methodVisitor.visitInsn(LSUB);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(14, label4);
methodVisitor.visitInsn(RETURN);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label5, 0);
methodVisitor.visitLocalVariable("var1", "J", null, label1, label5, 1);
methodVisitor.visitLocalVariable("var3", "J", null, label3, label5, 3);
methodVisitor.visitMaxs(6, 5);
methodVisitor.visitEnd();
}
{
// 生成method方法
methodVisitor = classWriter.visitMethod(0, "method", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(16, label0);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "Lcom/example/bytecodeinstdemo2/ASMDemo;", null, label0, label1, 0);
methodVisitor.visitMaxs(0, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
// 获取生成的class文件对应的二进制流
byte[] code = classWriter.toByteArray();
//将二进制流写到本地磁盘上
FileOutputStream fos = null;
try {
fos = new FileOutputStream(PATH + "ASMDemo.class");
fos.write(code);
fos.close();
//直接将二进制流加载到内存中
Helloworld loader = new Helloworld();
// Class<?> exampleClass = loader.defineClass("Example", code, 0, code.length);
//通过反射调用main方法
// exampleClass.getMethods()[0].invoke(null, new Object[] { null });
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面的代码等同于最开始我们贴出来的源码。
参考文章:
https://blog.csdn.net/zhangjg_blog/article/details/22976929
https://www.jianshu.com/p/16ed4d233fd1
https://www.jianshu.com/p/13d18c631549