ASM
ASM是一个字节码处理框架,只要是做代码插桩,都离不开这个库,使用方式上有两种,一个是tree api,一个是core api,引入方式如下
implementation 'org.ow2.asm:asm:9.7.1'
core api
core api主要是使用的访问者涉及模式,可以看到都是通过回调的形式来处理
通用使用方式
1、创建ClassReader实例来读取原有的类。
2、创建ClassWriter实例,它将会用于生成修改后的字节码。
3、设置ClassVisitor链,通过自定义的ClassVisitor来扫描类,并在遇到特定方法时,使用LogMethodAdapter。
创建ClassVisitor遍历类
创建一个ClassVisitor(如之前定义的LogMethodClassAdapter),在访问到方法时,使用LogMethodAdapter。
public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
// 指定需要插入日志的方法
if (name.equals("exampleMethod")) {
return new LogMethodAdapter(Opcodes.ASM9, mv, access, name, desc);
}
return mv;
}
}
创建MethodVisitor遍历方法
public class LogMethodAdapter extends MethodVisitor {
public LogMethodAdapter(int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
}
@Override
public void visitCode() {
super.visitCode();
// 在方法开始处添加日志
visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
visitLdcInsn("方法开始");
visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
@Override
public void visitInsn(int opcode) {
// 在方法返回前添加日志
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
visitLdcInsn("方法结束");
visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
}
组合使用
String classFilePath = "/path/to/ExampleClass.class";
FileInputStream is = new FileInputStream(classFilePath);
// 使用ClassReader读取原始字节码
ClassReader cr = new ClassReader(is);
// ClassWriter负责生成修改后的字节码
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// 设置ClassVisitor链
MyClassVisitor cv = new MyClassVisitor(cw);
// 开始处理
cr.accept(cv, ClassReader.EXPAND_FRAMES);
// 获取修改后的类字节码
byte[] b = cw.toByteArray();
// 将修改后的字节码写入新的文件
FileOutputStream fos = new FileOutputStream("/path/to/ModifiedExampleClass.class");
fos.write(b);
fos.close();
tree api
tree api主要是使用ClassNode,通过ClassNode 可以获取所有的字段、方法等
添加依赖
implementation 'org.ow2.asm:asm-tree:9.2'
举例说明
public class LogMethodTransformerWithTreeAPI {
public static void main(String[] args) throws IOException {
String classFilePath = "/path/to/ExampleClass.class";
FileInputStream is = new FileInputStream(classFilePath);
ClassReader classReader = new ClassReader(is);
ClassNode classNode = new ClassNode();
classReader.accept(classNode, 0);
for (MethodNode mn : classNode.methods) {
if ("exampleMethod".equals(mn.name)) {
InsnList instructions = mn.instructions;
Iterator<AbstractInsnNode> iterator = instructions.iterator();
while (iterator.hasNext()) {
AbstractInsnNode insnNode = iterator.next();
// 在方法开始处插入日志
if (insnNode.getOpcode() == -1) { // 方法入口
InsnList listToAdd = new InsnList();
listToAdd.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
listToAdd.add(new LdcInsnNode("方法开始"));
listToAdd.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
instructions.insertBefore(insnNode, listToAdd);
}
// 检测方法返回点,在返回前插入日志
if ((insnNode.getOpcode() >= IRETURN && insnNode.getOpcode() <= RETURN) || insnNode.getOpcode() == ATHROW) {
InsnList listToAdd = new InsnList();
listToAdd.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
listToAdd.add(new LdcInsnNode("方法结束"));
listToAdd.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
instructions.insertBefore(insnNode, listToAdd);
}
}
}
}
// 将修改后的类写回字节码
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
classNode.accept(classWriter);
byte[] b = classWriter.toByteArray();
FileOutputStream fos = new FileOutputStream("/path/to/ModifiedExampleClass.class");
fos.write(b);
fos.close();
}
}
ClassNode: 代表了整个类。我们使用ClassReader来填充它,从而读取整个类的结构到内存中。
MethodNode: 代表了ClassNode中的一个方法。我们遍历ClassNode中的方法,寻找我们想要修改的那个。
InsnList 和 AbstractInsnNode: 表示方法中的指令列表和单个指令。我们在方法的指令列表中寻找特定的点,插入我们的日志记录指令。
与Core API相比,Tree API让我们能以更抽象、更直观的方式查看和修改类的结构。这在处理复杂的逻辑时尤其有用,因为它允许一次性查看和修改整个方法或类,而不是逐条处理指令。
缺点也有,内存使用增加: 因为整个类结构都被读取到内存中,对于大型项目可能会导致内存使用增加。
性能: 相比于直接在读取过程中修改字节码(Core API),Tree API需要额外的时间来构建和处理整个类的树形结构。
再举个例子
替换指定类的父类
val classReader = ClassReader(writer.toByteArray())
val classNode = ClassNode()
classReader.accept(classNode, 0)
if (classNode.superName == "java/lang/Thread") {
classNode.replaceSuperName("com/demo/threadaop/transform/thread/ProxyThread")
}
val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS)
classNode.accept(classWriter)
zos.write(classWriter.toByteArray())
private fun ClassNode.replaceSuperName(superName: String) {
val originSuperName = this.superName
//替换父类
this.superName = superName
println("替换${this.name} >>> 父类${originSuperName} >>>> $superName")
for (method in methods) {
if ("<init>" == method.name) {
val instructions: InsnList = method.instructions
for (i in 0 until instructions.size()) {
val insnNode: AbstractInsnNode = instructions.get(i)
if (insnNode.opcode == Opcodes.INVOKESPECIAL) {
val methodInsnNode: MethodInsnNode = insnNode as MethodInsnNode
if (methodInsnNode.owner == originSuperName &&
methodInsnNode.name.equals("<init>")
) {
//构造函数也用父类的
methodInsnNode.owner = superName
}
}
}
}
}
}