“ 字节码插桩 “ 技术简介





一、" 字节码插桩 " 技术简介



性能优化 , 插件化 , 热修复 , 等技术都需要用到 " 字节码插桩 " 相关技术 ;


OOP 面向对象编程 , 主要针对业务逻辑进行开发 , 大特点封装 , 继承 , 多态 ; 大特点 封装 , 继承 , 多态 ; 大特点封装,继承,多态;

AOP 面向切面编程 , 主要针对某个动作进行开发 ;

  • 如 : 给 Android 中所有的 Activity 添加安全策略 , 如果每个页面逐个手动添加 , 可能存在遗漏 , 并且会造成代码冗余 ; 通过 AOP 面向切面编程 , 完成上述操作 ;

" 字节码插桩 " 技术应用 :

  • 代码生成 : 编译时生成代码 , 提高开发效率 , 减少手工工作量 , 降低出错概率 ;
  • 代码修改 : 为某些三方库添加崩溃 try catch 异常捕获机制 ;
  • 代码监控 : 编译时插桩 , 监控应用各种性能 , 如页面打开时间 , 页面停留时间 ; 友盟应该用了该技术 ;
  • 代码分析 : 使用编译时字节码插桩技术 , 自定义代码检查 ;

字节码插桩原理 : 使用 javac 编译出 .class 字节码文件之后 , 使用 ASM 或 AspectJ 修改 .class 字节码文件 , 然后使用 dx 工具将修改后的 .class 字节码文件打包到 .dex 文件中 ;

在这里插入图片描述





二、AspectJ 插桩工具



AspectJ 插桩工具 :

使用简单 : 使用 AspectJ 插桩工具修改字节码文件 , 不需要了解 .class 字节码文件的二进制格式 ;

成熟稳定 : 字节码操作 如果错了 个字节 , 整个字节码就无法正常工作 个字节 , 整个字节码就无法正常工作 个字节,整个字节码就无法正常工作 , 因此修改字节码操作 , 必须稳定 , 这也是插桩工具的必备条件 ;

固定切入点 : AspectJ 只能在固定的几个切入点插入 , 如 : 方法调用前 , 方法内部 , 异常前后 , 变量修改 ; 不能完成很细致的操作 , 如将某些特定规则的字节码序列作为切入点 ;

匹配规则 : AspectJ 的匹配规则类似于正则表达式 , 如 : 匹配 onXXX 方法 , 会匹配到 onCreate 方法 , 也会匹配到 onDestroy 方法 ;

性能低 : AspectJ 插入逻辑时 , 会添加一些额外冗余代码 , 生成的字节码肯定大于之前的字节码文件 , 对原来的性能也有一定影响 , 修改后的字节码文件 性能低于 修改前的字节码文件 ;





三、ASM 插桩工具



ASM 插桩工具 :

操作灵活 : 可以在字节码 任何位置 , 自定义修改 , 插入 , 删除 相关逻辑 ;

上手很难 : 使用 ASM 的前提是必须 对 Java 的 .class 字节码文件有比较深入的了解 ;

ASM字节码插桩 使用示例:
https://blog.csdn.net/qq_40977118/article/details/126099485

使用ASM可以基于注解在指定方法前后插桩,推荐使用。

示例:

待修改的类
package org.example.asm8.modify;

import javax.annotation.PostConstruct;

public class OriginalClass {

private int age;

public OriginalClass() {
}

private void methodA() {
    System.out.println("I am methodA!");
}

@PostConstruct
public void methodB() {
    System.out.println("I am methodB!");
}

public int methodC() {
    return age;
}

}

修改后的类

import javax.annotation.PostConstruct;

public class UpdatedClass {

private int age;

/**
 * 1.增加字段
 */
public String name;

/**
 * 2.删除methodA方法
 */

/**
 * 3.对有PostConstruct注解的方法进行增强
 */
@PostConstruct
public void methodB() {
    long start = System.currentTimeMillis();
    System.out.println("I am methodB!");
    long end = System.currentTimeMillis();
    System.out.println("spend time: " + (end - start) + " ms");
}

/**
 * 4.将public改成protected
 */
protected int methodC() {
    return this.age;
}

/**
 * 5.增加getName方法
 */
public String getName() {
    return name;
}

}

代码实现

package org.example.asm8.modify;

import java.io.FileOutputStream;
import java.util.Arrays;

import org.example.asm8.create.CreateHuman;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.AdviceAdapter;

public class ModifyOriginalClass implements Opcodes {

public static void main(String[] args) throws Exception {
    ClassReader cr = new ClassReader(OriginalClass.class.getName());
    ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
    cr.accept(new MyClassVisitor(ASM9, cw), 0);
    byte[] bytes = cw.toByteArray();
    // 生成class
    String path = CreateHuman.class.getResource("/").getPath() + "UpdatedClass.class";
    System.out.println("输出路径:" + path);
    try (FileOutputStream fos = new FileOutputStream(path)) {
        fos.write(bytes);
    }
}

static class MyClassVisitor extends ClassVisitor {

    protected MyClassVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    // 访问方法时
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
        String[] exceptions) {
        System.out.println("visit method: " + access + " " + name + " " + signature + " "
            + Arrays.toString(exceptions) + " ----> " + descriptor);
        // 2.在拷贝过程中删除methodA方法
        if ("methodA".equals(name)) {
            return null;
        }
        // 4.将methodC变成protected
        if ("methodC".equals(name)) {
            access = ACC_PROTECTED;
        }
        MethodVisitor methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions);
        return new MyMethodVisitor(api, methodVisitor, access, name, descriptor);
    }

    // 访问类结束前
    @Override
    public void visitEnd() {
        // 1.添加属性
        FieldVisitor fieldVisitor = cv.visitField(ACC_PUBLIC, "name", "Ljava/lang/String;", null, null);
        fieldVisitor.visitEnd();
        // 5.增加getName方法
        MethodVisitor methodVisitor = cv.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
        methodVisitor.visitCode();
        methodVisitor.visitVarInsn(ALOAD, 0);
        methodVisitor.visitFieldInsn(GETFIELD, "org/example/asm8/UpdatedClass", "name", "Ljava/lang/String;");
        methodVisitor.visitInsn(ARETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();
        super.visitEnd();
    }
}

/**
 * AdviceAdapter 是 MethodVisitor 的子类,它可以直接提供方法入口和出口的回调方法 onMethodEnter() 和 onMethodExit()
 * 可以对方法进行增强
 */
static class MyMethodVisitor extends AdviceAdapter {

    boolean flag = false;

    /**
     * Constructs a new {@link AdviceAdapter}.
     *
     * @param api
     *            the ASM API version implemented by this visitor. Must be one of the {@code
     *                      ASM}<i>x</i> values in {@link Opcodes}.
     * @param methodVisitor
     *            the method visitor to which this adapter delegates calls.
     * @param access
     *            the method's access flags (see {@link Opcodes}).
     * @param name
     *            the method's name.
     * @param descriptor
     *            the method's descriptor.
     */
    protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
        super(api, methodVisitor, access, name, descriptor);
        if ("methodB".equals(name)) {
            System.out.println("这是methodB方法");
        }
    }

    // 遇到注解时
    @Override
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        // 3.对有PostConstruct注解的方法进行增强
        if ("Ljavax/annotation/PostConstruct;".equals(descriptor)) {
            flag = true;
        }
        return super.visitAnnotation(descriptor, visible);
    }

    // 进入方法时
    @Override
    protected void onMethodEnter() {
        if (!flag) {
            return;
        }
        // 3.实现增强
        System.out.println("即将对有PostConstruct注解的方法进行增强====》");
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        mv.visitVarInsn(LSTORE, 1);
    }

    // 退出方法前
    @Override
    protected void onMethodExit(int opcode) {
        if (!flag) {
            return;
        }
        // 3.实现增强
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        mv.visitVarInsn(LSTORE, 3);
        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("spend time: ");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        mv.visitVarInsn(LLOAD, 3);
        mv.visitVarInsn(LLOAD, 1);
        mv.visitInsn(LSUB);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
        mv.visitLdcInsn(" ms");
        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);
    }
}

}

修改结果
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值