一、" 字节码插桩 " 技术简介
性能优化 , 插件化 , 热修复 , 等技术都需要用到 " 字节码插桩 " 相关技术 ;
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);
}
}
}
修改结果