|
ASM 字节码增强
409 Java 之所以能够实现“一次编译,到处运行”是因为 Java 源代码经过编译器编译后生成的是固定格式的字节码(.class)文件,而不是特定于某个平台的本机机器代码。字节码是一种中间代码,它与特定平台无关。并且每个支持 Java 的平台都需要有相应的 JVM,负责解释和执行字节码。Java 中使用命令 javac [options] 编译源码,一个 .java 源码文件从编译到运行的示例图:Java 字节码结构public class ByteCodeDemo { private String prefix = "A"; public String getPrefix() { return prefix; }}对应字节码文件 ByteCodeDemo.class根据 JVM 规范,每个 class 文件具有固定的数据结构。 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 https://zhuanlan.zhihu.com/p/94498015?hmsr=toutiao.io; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }可以看到 class 文件由固定结构构成:魔数、版本号、常量定义、访问标志、类索引、父类索引、接口个数和索引表、字段个数和索引表、方法个数和索引表、属性个数和索引表。字节码结构详细解释,可参考官方文档:Chapter 4. The class File Format字节码查看工具这里介绍三种查看字节码命令的方式方式一:JDK 工具包的 bin 目录下提供的 javap,该工具可以查看 Java 编译后的 class 文件,使用命令如下命令进行查看。在 class 文件目录下执行 javap -c 文件名.class ,输出 class 字节码文件。方式二:Idea 插件 Bytecode Viewer。在 class 文件中点击菜单 view -> Show Bytecode插件输出字节码文件方式三:Idea 插件 Jclasslib Bytecode Viewer。在 class 文件中点击菜单 view -> Show Bytecode With Jclasslib插件输出字节码信息此插件会分好类,对于不认识的字节码指令,可以直接跳转 JDK 官网的字节码命令网页地址。字节码增强字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。以下是一些常见的 Java 字节码类库:ASM (Bytecode Manipulation Framework):简介:ASM 是一个轻量级的字节码操作框架,提供了生成和转换字节码的功能。它是一个强大的字节码工具,被广泛用于许多 Java 字节码操作的场景。官方网站:ASMByte Buddy:简介:Byte Buddy 是一个用于创建和操作字节码的库。它提供了一个高层次的 API,用于动态创建类、生成代理和拦截方法调用等。官方网站:Byte BuddyJavassist:简介:Javassist 是一个用于在运行时编辑字节码的库。它提供了简单的 API,允许开发者在不需要事先编译的情况下修改类的结构。官方网站:JavassistCGlib (Code Generation Library):简介:CGlib 是一个字节码生成库,它扩展了 Java 类。它常用于生成动态代理对象和拦截方法调用。GitHub 地址:CGlibBCEL (Byte Code Engineering Library):简介:BCEL 是一个用于分析、创建和修改 Java 字节码的库。它提供了许多类和方法,用于处理类文件的各个方面。官方网站:BCELJBC (Java Bytecode Editor):简介:JBC 是一个简单的 Java 字节码编辑器,它提供了一个图形用户界面,用于浏览、编辑和修改字节码。GitHub 地址:JBC本文则介绍 ASM 字节码增强类库ASMASM 是一个 Java 字节码操作和解析框架。ASM 可以在类被加载入 JVM 之前动态修改已存在类行为,也可以直接生成 .class 字节码文件。ASM 提供了和其他字节码框架相似的功能,整个类包非常小,不到120KB,但其非常注重对类字节码的操作速度。这种高性能来自于它的设计模式 - 访问者模式,即通过 Reader、Visitor 和 Writer 模式。ASM 是直接操作类字节码数据,因此其读写对象是字节码指令。ASM API 从组成结构上可以分成两部分,一部分为 Core API,另一部分为 Tree API:ASM Core API 包括 asm.jar、asm-util.jar 和 asm-commons.jar。ASM Tree API 包括 asm-tree.jar 和 asm-analysis.jar。Core API 是基础,Tree API 也是基于 Core API 构建的。ASM Core APIASM Core API 使用流式的方式根据字节码结构从上到下依次处理,性能很好,所以一般 ASM 增强字节码一般都使用 Core API。核心类:ClassReader:读取字节码并将其转换为内部数据结构。ClassWriter:将内部数据结构转换回字节码,允许对字节码进行修改。ClassVisitor:字节码访问者接口,通过它可以在访问字节码的过程中进行操作。CoreAPI 根据字节码结构从上到下依次处理,对于字节码文件中不同的区域有不同的 Visitor,比如用于访问方法的 MethodVisitor、用于访问类变量的 FieldVisitor、用于访问注解的 AnnotationVisitor 等。基于 Core API 进行 Class Transformation 处理流程在 Core API 中,使用 ClassReader、ClassWriter、ClassVisitor 类进行 Class Transformation 的整体思路是:ClassReader --> ClassVisitor[1] --> ...... --> ClassVisitor[N] --> ClassWriterClassVisitor 是 Class Transformation 的核心操作。通过 ClassVisitor 可以访问到字节码不同区域对应的 Visitor,通过对应的 Visitor 做相应的修改。ASM Tree APIASM Tree API 是 ASM 框架提供的一种基于树结构的字节码访问方式。将字节码文件读取到内存中构建树结构,通过各种 Node 类来映射字节码。与传统的基于事件的访问方式相比,Tree API 更直观,使开发者能够以树形结构的方式轻松分析和修改字节码。ASM Tree API包括 asm-tree.jar 和 asm-analysis.jar。asm-tree.jar主要类按“包含”组织关系:ClassNode:(类)VisitMethod(): 用于访问类中的方法。VisitField(): 用于访问类中的字段。Accept(): 接受一个访问者(Visitor),允许对类进行访问。描述:表示一个类的节点。它是整个树结构的根节点。方法:FieldNode:(字段)VisitAnnotation(): 用于访问字段的注解。描述:表示一个字段的节点。它是 ClassNode 的一个子节点。方法:MethodNode:(方法)VisitLocalVariable(): 用于访问方法的局部变量。VisitAnnotation(): 用于访问方法的注解。Instructions: 代表方法体中的指令列表。描述:表示一个方法的节点。它是 ClassNode 的一个子节点。方法:InsnList:(有序的指令集合)Add(): 添加一个指令到列表中。Accept(): 接受一个访问者,允许对指令列表进行访问。描述:表示一组字节码指令的列表。它通常由 MethodNode 的 Instructions 字段持有。方法:AbstractInsnNode:(单条指令)描述:表示字节码中的单个指令节点的抽象基类。子类:有各种具体的指令节点,例如 VarInsnNode、MethodInsnNode 等。这些类和接口之间的关系形成了一个树形结构,其中 ClassNode 是根节点,MethodNode 和 FieldNode 是其直接的子节点,而 InsnList 包含在 MethodNode 中。通过这个树形结构,开发者可以方便地分析和修改字节码,而不需要直接操作字节码数组。基于 ASM Tree API 进行 Class Transformation 处理流程ASM Tree API 进行 Class Transformation 的流程,是利用 Core API 处理流程。ClassReader --> ClassVisitor[1] --> ... --> ClassNode[M] -->... --> ClassVisitor[N] --> ClassWriter因为 ClassNode 类(Tree API)是继承 ClassVistor 类(Core API),因此两个处理流程本质一样的。这里需要考虑三点:如何利用 Core API(ClassReader 和 ClassVisitor)转为 Tree API(ClassNode)。如何将 Tree API (ClassNode)转为 Core API(ClassVisitor 和 ClassWriter)。如何对 ClassNode 转换。通过下文 Demo 演示使用方式。ASM 使用Core API 使用 DemoASM 版本使用 ASM9源码:public class ByteCodeDemo { private String prefix = "A"; public String getPrefix() { return prefix; } public void test() { System.out.println("ByteCodeDemo#test"); }}字节码操作:构建 Visitor 给每个方法开始和结尾输出标识import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Opcodes;public class ByteCodeDemoClassVisitor extends ClassVisitor implements Opcodes { public ByteCodeDemoClassVisitor(ClassVisitor cv) { super(ASM9, cv); } // 访问 class 文件时开始执行 @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); } // 访问字节码方法区时开始执行 @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("") & mv != null) { return mv; } // 构造方法处理 return new ByteCodeDemoMethodVisitor(mv); } private class ByteCodeDemoMethodVisitor extends MethodVisitor implements Opcodes { public ByteCodeDemoMethodVisitor(MethodVisitor mv) { super(ASM9, mv); } // 进入方法代码块开始时执行方法 @Override public void visitCode() { super.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("start"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } /** * 重写方法时就需要用 ASM 的写法,手动写入或者修改字节码。 * 通过调用 MethodVisitor 的 visitXXXXInsn() 方法就可以实现字节码的插入,XXXX 对应相应的操作码助记符类型, * 比如 mv.visitLdcInsn(“end”) 对应的操作码就是ldc “end”,即将字符串“end” 压入栈。 */ @Override public void visitInsn(int opcode) { if ((opcode >= Opcodes.IRETURN & opcode
|
|