参考文档:
- https://asm.ow2.io/asm4-guide.pdf
- https://asm.ow2.io/javadoc/org/objectweb/asm/tree/package-summary.html
- 《ASM 字节码编程实战》
- 《ASM 从入门到实战》
JVM字节码
javap
- javap 核心命令解析
| 命令 | 作用 |
|---|---|
| javap TestClass.class | 基础输出:类的访问修饰符、字段、方法签名 |
| javap -v TestClass.class | 详细输出(最常用):魔数、常量池、访问标志、字段表、方法表、字节码指令等 |
| javap -c TestClass.class | 仅输出方法的字节码指令 |
| javap -s TestClass.class | 输出字段 / 方法的签名(包含类型描述符) |
魔数:
Class 文件开头 4 个字节固定为0xCAFEBABE(是识别 Class 文件的标志)
常量池:
Constant pool部分,存储所有字符串、类名、方法名等常量,通过索引引用(如#1指向java/lang/Object.<init>());
Constant pool: // 常量池(Class文件的核心,存储字符串、类名、方法名等)
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = String #16 // JavaBytecode
#3 = Fieldref #5.#17 // TestClass.name:Ljava/lang/String;
#4 = Methodref #5.#15 // TestClass."<init>":()V
#5 = Class #18 // TestClass
#6 = Class #19 // java/lang/Object
访问标志:
如ACC_PUBLIC(公有的)、ACC_PRIVATE(私有的),标识类 / 字段 / 方法的修饰符;
类型描述符:
如(II)I表示 “入参 2 个 int,返回值 int”,Ljava/lang/String;表示 String 类型(引用类型以 L 开头,分号结尾);
字节码指令:
如iload_1(加载第一个 int 参数到操作数栈)、iadd(操作数栈顶两个 int 相加)、ireturn(返回 int 值)。
常用字节码指令
| 类别 | 核心作用 | 常用指令及说明 |
|---|---|---|
| ★数据操作指令 | 局部变量↔操作数栈的交互 | 1、iload_n:把局部变量表中第 n 个 int 变量压入栈(n=0/1/2/3,如iload_1)2、 istore_n:把栈顶 int 值存入局部变量表第 n 位 3、 ldc:把常量池中的常量(字符串 /int/float)压入栈 4、 aload_0:把当前对象(this)压入栈(引用类型) |
| ★算术运算指令 | 操作数栈上的数值计算 | 1、 iadd:栈顶两个 int 相加,结果压回栈 2、 isub/imul/idiv:int 减 / 乘 / 除 3、 ladd/dadd:long/double 相加(不同类型指令不同)4、 ineg:int 取反 |
| ★方法调用指令 | 调用类 / 对象的方法 | 1、 invokestatic:调用静态方法(如Math.abs())2、 invokevirtual:调用实例方法(如obj.toString())3、 invokespecial:调用构造方法 / 私有方法 4、 invokeinterface:调用接口方法 |
| 控制流指令 | 实现分支、循环、异常 | 1、ifeq:栈顶 int 为 0 则跳转(if (x==0))2、 ifne:栈顶 int 非 0 则跳转 3、 goto:无条件跳转(实现循环)4、 tableswitch:switch-case(整数类型)5、 athrow:抛出异常 |
| 类型转换指令 | 不同数值类型的转换 | 1、 i2l:int 转 long 2、 l2d:long 转 double 3、i2b:int 转 byte(JVM 中 byte/short 会先转 int 处理) |
| 对象操作指令 | 创建对象、访问字段 | 1、new:创建对象(如new TestClass)2、 getfield:获取对象的实例字段(如obj.name)3、 putfield:给对象的实例字段赋值 4、 getstatic:获取静态字段 |
| 栈操作指令 | 操作数栈的管理 | 1、pop:弹出栈顶 1 个元素2、 dup:复制栈顶元素并压入栈(如构造方法中复用 this)3、swap:交换栈顶两个元素 |
| 返回指令 | 方法返回值 | 1、 ireturn:返回 int 类型 2、 lreturn:返回 long 类型3、 areturn:返回引用类型 4、 return:无返回值(void 方法) |
示例
使用ASM操作字节码实现创建类和方法、改写类和方法、实现接口、继承父类覆写方法等
- 创建一个类
public class User {
private String name;
public User() {
this.name = "default";
}
public String getName() {
return this.name;
}
}
import org.objectweb.asm.*;
import java.io.FileOutputStream;
public class ASMCreateClassDemo {
public static void main(String[] args) throws Exception {
// 1. 创建ClassWriter:COMPUTE_FRAMES自动计算栈映射帧,COMPUTE_MAXS自动计算栈大小和局部变量表大小
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
// 2. 定义类的基础信息:版本、访问标志、类名、泛型签名、父类、实现的接口
// 参数说明:
// V1_8:JDK8版本 | ACC_PUBLIC:公有的 | "com/example/User":类的内部名(.替换为/)
// null:无泛型 | "java/lang/Object":父类(默认继承Object) | new String[0]:无实现接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/example/User", null, "java/lang/Object", null);
// 3. 添加私有字段:private String name
FieldVisitor fv = cw.visitField(
Opcodes.ACC_PRIVATE, // 访问标志:私有
"name", // 字段名
"Ljava/lang/String;", // 字段类型描述符(String类型)
null, // 泛型签名
null // 默认值
);
fv.visitEnd(); // 字段定义结束
// 4. 添加无参构造方法:public User() { this.name = "default"; }
MethodVisitor mv = cw.visitMethod(
Opcodes.ACC_PUBLIC, // 访问标志:公有
"<init>", // 构造方法固定名为<init>
"()V", // 方法描述符:无参,返回void
null, // 泛型签名
null // 异常表
);
// 构造方法必须先调用父类构造方法
mv.visitCode(); // 开始编写方法体字节码
mv.visitVarInsn(Opcodes.ALOAD_0); // 加载this对象到操作数栈
// 调用父类Object的构造方法
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
// 给name字段赋值:this.name = "default"
mv.visitVarInsn(Opcodes.ALOAD_0); // 加载this
mv.visitLdcInsn("default"); // 加载常量"default"到栈
// 给实例字段赋值:putfield 类名/字段名/类型描述符
mv.visitFieldInsn(Opcodes.PUTFIELD, "com/example/User", "name", "Ljava/lang/String;");
mv.visitInsn(Opcodes.RETURN); // 方法返回(void)
mv.visitMaxs(0, 0); // 栈大小和局部变量表大小(COMPUTE_MAXS会自动计算,填0即可)
mv.visitEnd(); // 构造方法结束
// 5. 添加getName方法:public String getName() { return this.name; }
MethodVisitor getNameMv = cw.visitMethod(
Opcodes.ACC_PUBLIC,
"getName",
"()Ljava/lang/String;", // 无参,返回String
null,
null
);
getNameMv.visitCode();
getNameMv.visitVarInsn(Opcodes.ALOAD_0); // 加载this
// 获取实例字段值:getfield
getNameMv.visitFieldInsn(Opcodes.GETFIELD, "com/example/User", "name", "Ljava/lang/String;");
getNameMv.visitInsn(Opcodes.ARETURN); // 返回引用类型(String)
getNameMv.visitMaxs(0, 0);
getNameMv.visitEnd();
// 6. 类定义结束
cw.visitEnd();
// 7. 生成字节码数组并写入文件(保存为User.class)
byte[] classBytes = cw.toByteArray();
try (FileOutputStream fos = new FileOutputStream("User.class")) {
fos.write(classBytes);
}
System.out.println("类创建成功!字节码已写入User.class");
}
}
改写已有类
import org.objectweb.asm.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class ASMModifyClassDemo {
public static void main(String[] args) throws Exception {
// 1. 读取原有User.class的字节码
byte[] originalClassBytes;
try (FileInputStream fis = new FileInputStream("User.class")) {
originalClassBytes = fis.readAllBytes();
}
// 2. 创建ClassReader(读)+ ClassWriter(写)+ ClassVisitor(修改)
ClassReader cr = new ClassReader(originalClassBytes);
// ClassWriter的参数:cr表示复用原有类的常量池,减少字节码体积
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
// 自定义ClassVisitor,拦截getName方法
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
// 重写visitMethod,拦截方法定义
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// 只拦截getName方法
if ("getName".equals(name) && "()Ljava/lang/String;".equals(desc)) {
// 获取原方法的MethodVisitor,并用自定义MethodVisitor包装
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new MethodVisitor(Opcodes.ASM9, mv) {
// 重写visitCode,替换原有方法体
@Override
public void visitCode() {
// 新的方法逻辑:return this.name + "_modified"
mv.visitVarInsn(Opcodes.ALOAD_0); // 加载this
mv.visitFieldInsn(Opcodes.GETFIELD, "com/example/User", "name", "Ljava/lang/String;");
mv.visitLdcInsn("_modified"); // 加载常量"_modified"
// 调用String的concat方法:String.concat(String)
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"java/lang/String",
"concat",
"(Ljava/lang/String;)Ljava/lang/String;",
false
);
mv.visitInsn(Opcodes.ARETURN); // 返回拼接后的字符串
mv.visitMaxs(0, 0);
}
};
}
// 其他方法保持不变
return super.visitMethod(access, name, desc, signature, exceptions);
}
};
// 3. 读取并修改类
cr.accept(cv, ClassReader.EXPAND_FRAMES);
// 4. 写入修改后的字节码
byte[] modifiedClassBytes = cw.toByteArray();
try (FileOutputStream fos = new FileOutputStream("User_modified.class")) {
fos.write(modifiedClassBytes);
}
System.out.println("类修改成功!字节码已写入User_modified.class");
}
}











网友评论