前几天,在Q群里有个大佬,展示了下 Android 做无痕埋点,觉得挺厉害的 问了下使用的是 AspectJ, 网上搜了下资料 ASM 比 AspectJ 更灵活,更轻量 所以准备学习下 复制代码
ASM 是一款轻量级的Java字节码操作仓库 复制代码
ASM 主要有几个类需要了解 而且需要对 Java字节码 比较熟悉 ClassReader 字节码的读取与分析引擎。它采用类似SAX的事件读取机制,每当有事件发生时,调用注册的ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor做相应的处理。 ClassVisitor 定义在读取Class字节码时会触发的事件,如类头解析完成、注解解析、字段解析、方法解析等 AnnotationVisitor 定义在解析注解时会触发的事件,如解析到一个基本值类型的注解、enum值类型的注解、Array值类型的注解、注解值类型的注解等 FieldVisitor 定义在解析字段时触发的事件,如解析到字段上的注解、解析到字段相关的属性等 MethodVisitor 定义在解析方法时触发的事件,如方法上的注解、属性、代码等。 ClassWriter 它实现了ClassVisitor接口,用于拼接字节码。 复制代码
idea / Android studio ASM Bytecode Viewer(对 Java字节码 不熟悉的话必备) 复制代码
class User { public static void main(String[] args) { show(); } public static void show(){ System.out.println("Hello World"); } } 复制代码
上图为一个 User类,要对 show() 方法,的耗时进行计算并打印 复制代码
解析类的监听器,解析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; } } 复制代码
解析方法的监听器,解析Method时会触发内部的方法 编写前若对 Java字节码 不熟悉 建议安装 ASM Bytecode Viewer 插件 先新建一个类 编写要注入的代码,然后用插件查看 复制代码
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); } } 复制代码
编写测试类 运行
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 文件,以及输出效果
字节码修改后的 User 类的 .class 文件以及输出效果