
在上一篇Javassist入门中,我们介绍了如何使用Javassist操作Java字节码,Javassist主要是利用Java源码以及反射机制来实现的。而今天将要介绍另一种能操作Java字节码的技术,也就是ASM。他相比Javassist更灵活,提供了更细粒度的控制。
ASM是一个通用的 Java 字节码操作和分析框架。它可用于修改现有类或动态生成类(直接以二进制形式)。ASM 提供了一些常见的字节码转换和分析算法,可从中构建自定义复杂转换和代码分析工具。ASM 提供与其他 Java 字节码框架类似的功能,但更注重性能。由于它的设计和实现尽可能小巧和快速,因此非常适合在动态系统中使用(但当然也可以以静态方式使用,例如在编译器中)。
官网地址:https://asm.ow2.io/

流程图如下:

ClassReader 用于读取 .class 文件的字节码,解析其内容并提供访问类结构的方法。常用的方法有:
使用方式:
ClassReader classReader = new ClassReader("java.lang.String");
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);ClassWriter 用于生成或修改类的字节码,并输出为字节数组,甚至可以生成新的字节码文件。常用的方法有:
使用方式:
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
byte[] modifiedClass = classWriter.toByteArray();
ClassVisitor 是访问类结构的核心接口,所有对类的操作都需要通过它完成。常用的方法有:
使用方式:
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
System.out.println("Class name:" + name);
}
};
MethodVisitor 用于访问和修改方法的字节码指令。常用的方法有:
使用方式:
MethodVisitor methodVisitor = new MethodVisitor(Opcodes.ASM9) {
@Override
public void visitCode() {
super.visitCode();
mv.visitLdcInsn("Hello, ASM!");
mv.visitInsn(Opcodes.ARETURN);
}
};
FieldVisitor 用于访问和修改类中的字段。常用方法有:
使用方式:
FieldVisitor fieldVisitor = new FieldVisitor(Opcodes.ASM9) {
@Override
public void visitEnd() {
System.out.println("Field visit finished.");
}
};
Opcodes 是 ASM 提供的一组常量,用于表示字节码中的操作码、访问标志等。常用的常量有:
使用方式:
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "myMethod", "()V", null, null);<!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.7.1</version>
</dependency>使用ASM操作字节码,创建一个MyClass类,并构造一个方法,方法打印hello world, i am from asm信息。
package org.example.asm;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class AsmDemo {
public static void main(String[] args) throws Exception {
// 创建一个 ClassWriter 实例,用于生成类的字节码
ClassWriter classWriter = new ClassWriter(0);
// 定义一个新的类 MyClass
// Opcodes.V1_8 表示 Java 8 的版本号
// Opcodes.ACC_PUBLIC 表示类的访问权限为 public
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "MyClass", null, "java/lang/Object", null);
// 添加默认的构造函数
// Opcodes.ACC_PUBLIC 表示构造函数的访问权限为 public
// <init> 表示构造函数的名称,必须为 <init>。在 Java 源代码中,编译器会自动为类生成构造方法(如果未显式声明),生成的构造方法在字节码中始终以 <init> 作为名称。<init> 是 JVM 规范中固定表示构造方法的名称。
// ()V 表示构造函数的参数类型为空,即该方法无参
MethodVisitor constructor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructor.visitCode();
// visitVarInsn 用于操作局部变量的字节码指令方法
// Opcodes.ALOAD 表示加载一个局部变量到操作数栈上,0表示当前对象 this
constructor.visitVarInsn(Opcodes.ALOAD, 0);
// Opcodes.INVOKESPECIAL 表示调用一个特殊方法,这里调用父类 Object 的构造函数
constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructor.visitInsn(Opcodes.RETURN); // 返回
constructor.visitMaxs(1, 1); // 设置操作数栈和局部变量表的最大深度
constructor.visitEnd(); // 结束构造函数的定义
// 添加 sayHello 方法,同上构造函数创建
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sayHello", "()V", null, null);
mv.visitCode();
// 获取 System.out
// Opcodes.GETSTATIC 表示从静态字段中获取值,这里获取 System.out
// Ljava/io/PrintStream; 表示 PrintStream 类型
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// 调用 PrintStream.println 方法
mv.visitLdcInsn("Hello world, i am from ASM!"); // 将字符串 "Hello from ASM!" 压入操作数栈
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); // 调用 println 方法
mv.visitInsn(Opcodes.RETURN); // 返回
mv.visitMaxs(2, 2); // 设置操作数栈和局部变量表的最大深度
mv.visitEnd(); // 结束 sayHello 方法的定义
// 完成类定义
classWriter.visitEnd();
// 获取生成的类的字节码
byte[] classData = classWriter.toByteArray();
// 创建自定义类加载器
MyClassLoader classLoader = new MyClassLoader();
// 使用自定义类加载器加载生成的类
Class<?> myClass = classLoader.defineClass("MyClass", classData);
// 创建 MyClass 的实例
Object instance = myClass.getDeclaredConstructor().newInstance();
// 调用 MyClass 的 sayHello 方法
myClass.getMethod("sayHello").invoke(instance);
}
// 自定义类加载器
static class MyClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] data) {
// 使用父类的 defineClass 方法定义类
return defineClass(name, data, 0, data.length);
}
}
}

上面介绍了基本的ASM用法以及API,在上一篇中讲述了Javassist的方式操作字节码。同样操作字节码的技术,我们对比一下两者的区别以及联系。
ASM:基于字节码指令的低级别操作,直接操作字节码,接近 JVM 的底层实现。开发者需要熟悉 JVM 字节码的结构(例如操作数栈、局部变量表、指令等),更灵活但也更复杂。
Javassist:基于高层级的 API,提供类似 Java 源代码的操作方式,无需直接理解和操作字节码指令。更加高抽象,适合快速开发动态字节码功能,易于理解和使用。
ASM:性能更高,因为它直接操作字节码,无额外的抽象层。更适合性能敏感的场景,例如框架底层实现或对运行时性能要求非常高的工具。
Javassist:性能略低于 ASM,因为其高层级 API 会引入一定的开销。
ASM:用于开发高性能框架和工具,例如 AOP 框架、性能监控工具等;需要对字节码做精细控制。适合在Spring、MyBatis 等框架使用 ASM 提供底层的字节码增强能力。
Javassist:用于快速开发动态字节码功能,例如动态代理、简单方法增强;更适合业务层代码增强场景。适合快速开发,例如动态生成 POJO 类、简单的性能监控工具等。
综上,其实不难可以看出,ASM更接近于字节码底层的操作手法,天然的更具备灵活性,但是相应的代码的可读性和学习难度也较高。而Javassist更多像是个二方包,将底层字节码的操作方式封装为可读性更强的API,更方便开发者进行调用。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。