目录
前言
本文速通下ASM最最萌新直观的部分,理解浅薄,纯小白文
pom依赖
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>9.2</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>9.2</version>
</dependency>
</dependencies>
ASM概念
ASM(Objectweb ASM)是一个轻量级的 Java 字节码操作框架,可以用于动态生成类、修改现有类的字节码以及分析类文件。ASM 提供了一种基于事件驱动的 API,使得开发者可以方便地操作和修改 Java 类的字节码。
Java字节码&ClassFile
Java 字节码是一种由 JVM 执行的二进制指令集,它是 Java 源代码编译后生成的中间形式。Java 字节码包含了 JVM 能够识别和执行的指令,通过这些指令可以实现对 Java 类的各种操作和逻辑。
ClassFile 结构则是描述 Java 类文件格式的一种规范,它定义了一个类文件的结构和包含的信息。在 ClassFile 结构中,包含了类的版本号、常量池、访问标志、字段表、方法表等内容,这些信息都是以特定的格式和顺序存储在类文件中的。
Java 字节码实际上就是存储在 ClassFile 结构中的具体指令和数据,它们是 JVM 执行的基本单位。当 JVM 加载一个类文件时,会解析其中的 ClassFile 结构,然后将其中的字节码指令加载到内存中,并按照指令顺序执行,从而实现对 Java 类的功能。
ClassFile结构
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
// method的方法体由Code属性表示
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
// u? 表示占用?个字节
核心理念:拆分修改重组
CoreAPI最重要的三个类:
ClassReader
: 读取字节码文件,并拆分为不同的部分(decompose)
ClassVisitor
: 对字节码中某一部分进行修改(modify)FieldVisitor、MethodVisitor
ClassWriter
: 将各个部分重组为完整的字节码文件(recompose)FieldWriter、MethodWriter
ClassReader用于读取字节码文件,ClassWriter用于生成字节码文件,ClassVisitor用于修改字节码
ClassWriter 本身就是一个 ClassVisitor 的实现类,它可以接受对类的访问和修改操作,并将修改后的类文件输出为字节数组。
当需要生成新的类文件时,通常会创建一个继承自 ClassVisitor 的自定义 Visitor 类,通过重写其中的方法来实现对类文件的定制化操作,然后将这个 Visitor 类传递给 ClassWriter,利用 ClassWriter 输出最终的类字节码。
需要修改类文件时,要以ClassReader为旧字节码的入口,ClassWriter为新字节码的出口,中间可以通过多个ClassVisitor来修改字节码。
修改字节码最简化模型
将下面的cv替换为我们自定义的ClassVisitor子类即可
ClassReader cr = new ClassReader("classfile");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
// TODO
};
cr.accept(cv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
cw.toByteArray();
代码示例
ASM修改类的基本信息
假设要将一个类的访问修饰符修改为 public,或添加一个实现的接口 "Comparable",下面是一个示例代码:
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
public class CustomClassVisitor extends ClassVisitor {
public CustomClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
// 修改类的访问修饰符为 public
super.visit(version, access | Opcodes.ACC_PUBLIC, name, signature, superName, interfaces);
// 添加一个实现的接口 "Comparable"
String[] newInterfaces = new String[interfaces.length + 1];
System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
newInterfaces[interfaces.length] = "java/lang/Comparable";
super.visit(version, access, name, signature, superName, newInterfaces);
}
}
public class ClassModifier {
public static byte[] modifyClass(byte[] originalClass) {
ClassReader cr = new ClassReader(originalClass);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
CustomClassVisitor cv = new CustomClassVisitor(cw);
cr.accept(cv, 0);
return cw.toByteArray();
}
}
ASM修改类的字段
当使用 ASM 修改类的字段时,可以通过自定义 ClassVisitor 的子类来实现。
假设要向一个类中添加一个名为 "newField" 的字段,下面是一个示例代码:
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
public class CustomClassVisitor extends ClassVisitor {
public CustomClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
// 添加一个名为 "newField" 的字段
if ("newField".equals(name)) {
return null; // 跳过已存在的字段
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public void visitEnd() {
// 在 visitEnd 方法中添加一个新字段 "newField"
FieldVisitor fv = super.visitField(Opcodes.ACC_PRIVATE, "newField", "Ljava/lang/String;", null, null);
if (fv != null) {
fv.visitEnd();
}
super.visitEnd();
}
}
public class ClassModifier {
public static byte[] modifyClass(byte[] originalClass) {
ClassReader cr = new ClassReader(originalClass);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
CustomClassVisitor cv = new CustomClassVisitor(cw);
cr.accept(cv, 0);
return cw.toByteArray();
}
}
ASM修改类的方法
当使用 ASM 修改类的方法时,同样可以通过自定义 ClassVisitor 的子类来实现。
假设我们要向一个类中添加一个方法进入和退出时打印日志的方法,下面是一个示例代码:
常规实现
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MethodInOutVisitor extends ClassVisitor {
private final String methodName;
private final String methodDesc;
public MethodInOutVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) {
super(api, classVisitor);
this.methodName = methodName;
this.methodDesc = methodDesc;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (name.equals(methodName) && descriptor.equals(methodDesc)) {
mv = new MethodExitAdapter(api, mv);
}
return mv;
}
private static class MethodExitAdapter extends MethodVisitor {
public MethodExitAdapter(int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
}
@Override
public void visitCode() {
super.visitCode();
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitLdcInsn("Method Enter...");
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.ATHROW || (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitLdcInsn("Method Exit...");
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
}
}
AdviceAdapter实现
AdviceAdapter
是 ASM 框架中的一个类,用于在方法的进入和退出时插入额外的字节码指令。它是MethodVisitor
的子类,提供了便捷的方法来处理方法的进入和退出逻辑。通过继承
AdviceAdapter
类,可以重写其中的onMethodEnter
和onMethodExit
方法,在这两个方法中可以添加需要在方法进入和退出时执行的逻辑。例如,在onMethodEnter
方法中可以插入代码来记录方法的进入,并在onMethodExit
方法中可以插入代码来记录方法的退出或处理方法的返回值等操作。
AdviceAdapter
简化了在方法级别进行字节码操作的流程,使开发者能够更方便地在方法进入和退出时插入自定义的逻辑,实现一些在运行时对方法进行监控、调试或增强的需求。
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;
public class MethodInOutVisitor extends ClassVisitor {
private final String methodName;
private final String methodDesc;
public MethodInOutVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) {
super(api, classVisitor);
this.methodName = methodName;
this.methodDesc = methodDesc;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (name.equals(methodName) && descriptor.equals(methodDesc)) {
mv = new AdviceAdapter(api, mv, access, name, descriptor) {
@Override
protected void onMethodEnter() {
getStatic(System.class, "out", "Ljava/io/PrintStream;");
visitLdcInsn("Method Enter...");
invokeVirtual(Type.getType("Ljava/io/PrintStream;"), getMethod("println", "(Ljava/lang/String;)V"));
}
@Override
protected void onMethodExit(int opcode) {
if (opcode == ATHROW || (opcode >= IRETURN && opcode <= RETURN)) {
getStatic(System.class, "out", "Ljava/io/PrintStream;");
visitLdcInsn("Method Exit...");
invokeVirtual(Type.getType("Ljava/io/PrintStream;"), getMethod("println", "(Ljava/lang/String;)V"));
}
}
};
}
return mv;
}
}
标签:CoreAPI,Web,字节,ClassVisitor,u2,asm,public,ASM,String
From: https://blog.csdn.net/uuzeray/article/details/137089940