ASM简单入门笔记

栏目: Java · 发布时间: 5年前

内容简介:编写测试类 运行原User 类生成的 .class 文件,以及输出效果字节码修改后的 User 类的 .class 文件以及输出效果
前几天,在Q群里有个大佬,展示了下 Android 做无痕埋点,觉得挺厉害的
问了下使用的是 AspectJ, 网上搜了下资料 ASM 比 AspectJ 更灵活,更轻量
所以准备学习下
复制代码

2. 介绍

ASM 是一款轻量级的 Java 字节码操作仓库
复制代码

3. 前期准备

3.1 简单的asm 方面的知识

ASM 主要有几个类需要了解 而且需要对 Java字节码 比较熟悉

ClassReader
    字节码的读取与分析引擎。它采用类似SAX的事件读取机制,每当有事件发生时,调用注册的ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor做相应的处理。

ClassVisitor
    定义在读取Class字节码时会触发的事件,如类头解析完成、注解解析、字段解析、方法解析等

AnnotationVisitor
    定义在解析注解时会触发的事件,如解析到一个基本值类型的注解、enum值类型的注解、Array值类型的注解、注解值类型的注解等

FieldVisitor
    定义在解析字段时触发的事件,如解析到字段上的注解、解析到字段相关的属性等
    
MethodVisitor
    定义在解析方法时触发的事件,如方法上的注解、属性、代码等。

ClassWriter
    它实现了ClassVisitor接口,用于拼接字节码。
复制代码

3.2 开发 工具 准备

idea / Android studio 
ASM Bytecode Viewer(对 Java字节码 不熟悉的话必备)
复制代码

4 实战

4.1 要实现的效果

class User {
    public static void main(String[] args) {
        show();
    }

    public static void show(){
        System.out.println("Hello World");
    }
}
复制代码
上图为一个 User类,要对 show() 方法,的耗时进行计算并打印
复制代码

4.2 编写 ASM 逻辑

4.2.1 编写 ClassVisitor

解析类的监听器,解析Class字节码时会触发内部的方法
复制代码
public class TestClassVisitor extends ClassVisitor {
    public TestClassVisitor(final ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        if (cv != null) {
            cv.visit(version, access, name, signature, superName, interfaces);
        }
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        //如果methodName是show,则返回我们自定义的TestMethodVisitor
        if ("show".equals(name)) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            return new TestMethodVisitor(mv);
        }
        if (cv != null) {
            return cv.visitMethod(access, name, desc, signature, exceptions);
        }
        return null;
    }
}
复制代码

4.2.2 编写 MethodVisitor

解析方法的监听器,解析Method时会触发内部的方法
编写前若对 Java字节码 不熟悉 建议安装 ASM Bytecode Viewer 插件

先新建一个类 编写要注入的代码,然后用插件查看
复制代码
ASM简单入门笔记
public class TestMethodVisitor extends MethodVisitor implements Opcodes {
    public TestMethodVisitor(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitCode() {
        //方法体内开始时调用
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        mv.visitVarInsn(LSTORE, 0);
        super.visitCode();
    }
    @Override
    public void visitInsn(int opcode) {
        //每执行一个指令都会调用
        if (opcode == Opcodes.RETURN) {
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(LLOAD, 0);
            mv.visitInsn(LSUB);
            mv.visitVarInsn(LSTORE, 2);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLineNumber(11, l3);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            mv.visitLdcInsn("== method cost time = ");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitVarInsn(LLOAD, 2);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
            mv.visitLdcInsn(" ==");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

        }
        super.visitInsn(opcode);
    }
}
复制代码

4.3 测试效果

编写测试类 运行

public class Demo {
    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader(User.class.getName());
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new TestClassVisitor(cw);
        cr.accept(cv, Opcodes.ASM5);
        // 获取生成的class文件对应的二进制流
        byte[] code = cw.toByteArray();
        //将二进制流写到out/下
        FileOutputStream fos = new FileOutputStream("out/User.class");
        fos.write(code);
        fos.close();
    }
}
复制代码

原User 类生成的 .class 文件,以及输出效果

ASM简单入门笔记
ASM简单入门笔记

字节码修改后的 User 类的 .class 文件以及输出效果

ASM简单入门笔记
ASM简单入门笔记

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

结网@改变世界的互联网产品经理

结网@改变世界的互联网产品经理

王坚 / 人民邮电出版社 / 2013-5-1 / 69.00元

《结网@改变世界的互联网产品经理(修订版)》以创建、发布、推广互联网产品为主线,描述了互联网产品经理的工作内容,以及应对每一部分工作所需的方法和工具。产品经理的工作是围绕用户及具体任务展开的,《结网@改变世界的互联网产品经理(修订版)》给出的丰富案例以及透彻的分析道出了从发现用户到最终满足用户这一过程背后的玄机。新版修改了之前版本中不成熟的地方,强化了章节之间的衔接,解决了前两版中部分章节过于孤立......一起来看看 《结网@改变世界的互联网产品经理》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具