前几天,在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 Hello {
public static void main(String[] args) {
show();
}
public static void show(){
System.out.println("Hello World");
}
}
复制代码
上图为一个 Hello 类,要对 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 插件!!!
建议安装 ASM Bytecode Viewer 插件!!!
建议安装 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);
}
}
复制代码
编写测试类 运行java
public class Demo {
public static void main(String[] args) throws IOException {
ClassReader cr = new ClassReader(Hello.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/Hello.class");
fos.write(code);
fos.close();
}
}
复制代码
原 Hello 类生成的 .class 文件,以及输出效果git
字节码修改后的 Hello 类的 .class 文件以及输出效果github
能够用于无痕埋点,打印日志,以及性能监控等
复制代码
Githubapp