Java ASM

Java ASM 简介

ASM 简介

Java ASM(Java Assembly Language)是一个 Java 字节码操作框架,它可以通过修改字节码来实现代码的动态生成和修改。它可以用于许多应用场景,如代码转换、优化、代码生成、动态字节码增强等。在 Java 开发中,ASM 已经成为一个非常重要的编程工具

ASM 和 Javassist 都是 Java 字节码操作库。它们的主要区别在于 ASM 是一个基于事件模型的库,而 Javassist 是一个基于源代码的库。ASM 提供了一个 API,可以以字节码的形式读取、修改和生成 Java 类。它的设计具有高速和轻量级的特点,常常被用来进行一些高级的优化和增强。Javassist 则允许开发者以 Java 代码的形式来生成、修改和操作类文件,使用起来更加方便。两者虽然有不同的设计目的和操作方式,但在某些场景下可以达到相同的效果

我们编写一个最基础的 Java 程序:

1
2
3
4
5
public class Main {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}

查看它的字节码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ javap -c Main.class
Compiled from "Main.java"
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, world!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}

ASM 字节码处理流程:目标类 class bytes->ClassReader 解析->ClassVisitor 增强修改字节码->ClassWriter 生成增强后的 class bytes->通过 Instrumentation 解析加载为新的 Class,ASM 框架中的核心类有以下几个:

  • ClassReader:负责解析.class文件中的字节码,并将所有字节码传递给 ClassWriter
  • ClassVisitor:负责访问.class文件的各个元素,可以解析或者修改.class文件的内容
  • ClassWriter:继承自 ClassVisitor,它是生成字节码的工具类,负责将修改后的字节码输出为 byte 数组
  • ClassAdapter:该类也实现了 ClassVisitor 接口,它将对它的方法调用委托给另一个 ClassVisitor 对象

在使用 Java ASM 操作字节码时,我们需要了解以下基本概念:

  1. 类:Java 字节码表示的是 Java 类,包括类的名称、修饰符、父类、接口、字段和方法等信息
  2. 方法:类中的方法由方法描述符、方法签名、返回值类型、参数类型、局部变量表和指令集等信息组成
  3. 字段:类中的字段包括字段名、字段描述符、字段签名、修饰符和初始值等信息
  4. 指令:Java 字节码指令是 JVM 可以执行的低级操作,例如加载常量、执行算术运算、调用方法和访问字段等

需要的依赖:

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
</dependency>
</dependencies>

读取和解析字节码

加载字节码

要读取字节码,我们需要首先创建一个 ClassReader 实例。ClassReader 可以接受一个字节数组,表示一个已编译的 Java 类文件。例如,我们可以从文件系统或者类加载器中加载字节码:

1
2
3
4
5
6
7
8
9
// 从文件系统中加载字节码
byte[] bytecode = Files.readAllBytes(Paths.get("path/to/MyClass.class"));

// 或者从类加载器中加载字节码
InputStream is = getClass().getClassLoader().getResourceAsStream("com/example/MyClass.class");
byte[] bytecode = is.readAllBytes();

// 创建 ClassReader 实例
ClassReader classReader = new ClassReader(bytecode);

解析字节码

要解析字节码,我们需要创建一个自定义的 ClassVisitor 实现。以下是一个简单的示例,用于打印类名和方法名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MyClassVisitor extends ClassVisitor {
// 使用 ASM5 作为 Opcodes 版本,并调用父类构造函数
public MyClassVisitor() {
super(Opcodes.ASM5);
}

// 重写 visit 方法,用于在访问类时输出类名
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
// 打印类名
System.out.println("Class: " + name);
// 调用父类 visit 方法,以便继续处理类信息
super.visit(version, access, name, signature, superName, interfaces);
}

// 重写 visitMethod 方法,用于在访问类中的方法时输出方法名
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
// 打印方法名
System.out.println("Method: " + name);
// 调用父类 visitMethod 方法,以便继续处理方法信息
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}

visit()方法参数说明:

  1. version:类文件的版本号,表示类文件的 JDK 版本。例如,JDK1.8 对应的版本号为 52(0x34),JDK 11 对应的版本号为 55(0x37)
  2. access:类访问标志,表示类的访问权限和属性。例如,ACC_PUBLIC(0x0001)表示类是公共的,ACC_FINAL(0x0010)表示类是 final 的。可以通过位运算组合多个访问标志
  3. name:类的内部名称,用斜线代替点分隔包名和类名。例如com/example/MyClass
  4. signature:类的泛型签名,如果类没有泛型信息,此参数为 null
  5. superName:父类的内部名称。对于除java.lang.Object之外的所有类,此参数都不为 null
  6. interfaces:类实现的接口的内部名称数组。如果类没有实现任何接口,此参数为空数组

visitMethod()方法参数说明:

  1. access:方法访问标志,表示方法的访问权限和属性。例如,ACC_PUBLIC(0x0001)表示方法是公共的,ACC_STATIC(0x0008)表示方法是静态的。可以通过位运算组合多个访问标志
  2. name:方法的名称。例如,doSomething<init>(构造方法)
  3. descriptor:方法的描述符,表示方法的参数类型和返回值类型。例如,对于方法void doSomething(int),描述符为(I)V
  4. signature:方法的泛型签名,如果方法没有泛型信息,此参数为 null
  5. exceptions:方法抛出的异常的内部名称数组。如果方法没有声明抛出任何异常,此参数为空数组

我们的实验类:

1
2
3
4
5
6
7
8
9
public class Foo {
public void execute() {
System.out.println("test changed method name");
}

public void changeMethodContent() {
System.out.println("test changed method");
}
}

主类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;

import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws Exception {
ClassVisitor myClassVisitor = new MyClassVisitor();
ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get("E:\\Zodog\\Temp\\javassist\\ASM\\target\\classes\\Foo.class")));

classReader.accept(myClassVisitor, 0);
}
}

可以看见输出:

1
2
3
4
Class: Foo
Method: <init>
Method: execute
Method: changeMethodContent

修改字节码

对字段的操作

要添加、修改或删除字段,我们需要扩展 ClassVisitor 类并重写 visitField 方法,下面是一个示例,用于在类中添加一个名为newField的字段,并删除名为toBeRemovedField的字段,我们的测试类:

1
2
3
4
5
6
7
8
9
10
11
public class Foo {
String toBeRemovedField;

public void execute() {
System.out.println("test changed method name");
}

public void changeMethodContent() {
System.out.println("test changed method");
}
}

主要的逻辑代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;

public class MyFieldClassVisitor extends ClassVisitor {

public MyFieldClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM5, classVisitor);
}

@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
// 删除名为 toBeRemovedField 的字段
if ("toBeRemovedField".equals(name)) {
return null;
}

return super.visitField(access, name, descriptor, signature, value);
}

@Override
public void visitEnd() {
// 添加名为 newField 的字段
FieldVisitor newFieldVisitor = super.visitField(Opcodes.ACC_PRIVATE, "newField", "Ljava/lang/String;", null, null);
if (newFieldVisitor != null) {
newFieldVisitor.visitEnd();
}
super.visitEnd();
}
}

visitField()的方法参数说明:

  1. access:字段访问标志,表示字段的访问权限和属性。例如,ACC_PUBLIC(0x0001)表示字段是公共的,ACC_STATIC(0x0008)表示字段是静态的。可以通过位运算组合多个访问标志
  2. name:字段的名称。例如myField
  3. descriptor:字段的描述符,表示字段的类型。例如,对于类型为 int 的字段,描述符为I
  4. signature:字段的泛型签名,如果字段没有泛型信息,此参数为 null
  5. value:字段的常量值,如果字段没有常量值,此参数为 null。需要注意的是,只有静态且已赋值的字段才会有常量值

我们的主类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;

import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws Exception {
ClassVisitor myClassVisitor = new MyClassVisitor();
ClassVisitor myFieldClassVisitor = new MyFieldClassVisitor(myClassVisitor);
ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get("E:\\Zodog\\Temp\\javassist\\ASM\\target\\classes\\Foo.class")));

classReader.accept(myFieldClassVisitor, 0);
}
}

对方法的操作

要添加、修改或删除方法,我们需要扩展 ClassVisitor 类并重写 visitMethod 方法。下面是一个示例,用于在类中添加一个名为newMethod的方法,并删除名为toBeRemovedMethod的方法,测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Foo {
String toBeRemovedField;

public void execute() {
System.out.println("test changed method name");
}

public void changeMethodContent() {
System.out.println("test changed method");
}

public void toBeRemovedMethod() {
System.out.println("toBeRemovedMethod");
}
}

我们的主类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;

import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws Exception {
ClassVisitor myClassVisitor = new MyClassVisitor();
ClassVisitor myFieldClassVisitor = new MyFieldClassVisitor(myClassVisitor);
ClassVisitor myMethodClassVisitor = new MyMethodClassVisitor(myFieldClassVisitor);
ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get("E:\\Zodog\\Temp\\javassist\\ASM\\target\\classes\\Foo.class")));

classReader.accept(myMethodClassVisitor, 0);
}
}

可以在输出里看到变化:

1
2
3
4
5
Class: Foo
Method: <init>
Method: execute
Method: changeMethodContent
Method: newMethod

修改方法指令

要修改方法内的指令,我们需要扩展 MethodVisitor 类并重写相应的 visit 方法。以下是一个示例,用于在每个方法调用前添加一条打印日志的指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MyMethodAdapter extends MethodVisitor {

public MyMethodAdapter(MethodVisitor methodVisitor) {
super(Opcodes.ASM5, methodVisitor);
}

@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
// 在方法调用前添加 System.out.println
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Entering method: " + name);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

// 原始方法调用
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}

要应用这个方法适配器,我们需要在自定义的 ClassVisitor 实现中重写 visitMethod 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MyMethodLoggerClassVisitor extends ClassVisitor {

public MyMethodLoggerClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM5, classVisitor);
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
return new MyMethodAdapter(methodVisitor);
}
}

如此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws Exception {
ClassWriter classWriter = new ClassWriter(0);
MyMethodLoggerClassVisitor myMethodLoggerClassVisitor = new MyMethodLoggerClassVisitor(classWriter);
ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get("E:\\Zodog\\Temp\\javassist\\ASM\\target\\classes\\Foo.class")));

classReader.accept(myMethodLoggerClassVisitor, 0);
}
}

生成新的字节码

在修改字节码后,我们需要使用 Class Writer 生成新的字节码。以下是一个示例,展示了如何使用自定义的 ClassVisitor 修改字节码,并使用 ClassWriter 生成新的字节码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws Exception {
// 创建 ClassWriter
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
// 创建自定义的 ClassVisitor,接受 ClassWriter 作为参数
MyMethodLoggerClassVisitor myMethodLoggerClassVisitor = new MyMethodLoggerClassVisitor(classWriter);
// 创建 ClassReader
ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get("E:\\Zodog\\Temp\\javassist\\ASM\\target\\classes\\Foo.class")));

// 使用 ClassReader 遍历字节码,应用自定义的 ClassVisitor
classReader.accept(myMethodLoggerClassVisitor, ClassReader.EXPAND_FRAMES);

// 从 ClassWriter 中获取修改后的字节码
byte[] modifiedBytecode = classWriter.toByteArray();

// 可以将 modifiedBytecode 写入到 .class 文件或直接加载到 JVM 中执行
// 这里直接加载到 JVM 中执行
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> clazz = myClassLoader.defineClass("Foo", modifiedBytecode);
Object object = clazz.newInstance();

String[] methods = {"execute", "changeMethodContent", "toBeRemovedMethod"};
for (String method : methods) {
Method m = clazz.getMethod(method);
m.invoke(object);
}
}

static class MyClassLoader extends ClassLoader {
Class<?> defineClass(String name, byte[] bytes) {
return defineClass(name, bytes, 0, bytes.length);
}
}
}

在这里我们需要defineClass方法来加载字节码成为一个类,但是在 ClassLoader 里,defineClass被定义为protected,所以我们需要自定义一个类加载器来改变这个方法的访问修饰符,这样我们就可以通过这个自定义的类加载器调用对应的方法

高级技巧

自定义类加载器

要实现自定义类加载器,我们需要扩展 Java 标准库中的 ClassLoader 类,并重写 findClass 方法。以下是一个示例,展示了如何实现一个简单的自定义类加载器,它使用 Java ASM 修改类字节码后加载类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import java.io.*;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;

public class Main {
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader();

myClassLoader.FileSystemClassLoader("E:\\Temp");
Class<?> clazz = myClassLoader.loadClass("Foo");
Object object = clazz.newInstance();

String[] methods = {"execute", "changeMethodContent", "toBeRemovedMethod"};
for (String method : methods) {
Method m = clazz.getMethod(method);
m.invoke(object);
}
}
}

class MyClassLoader extends ClassLoader {
private String rootDir;

public void FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);

if (classData == null) {
throw new ClassNotFoundException();
} else {
// 使用 Java ASM 修改字节码
byte[] modifiedBytecode = modifyBytecode(classData);

// 使用修改后的字节码定义类
ByteBuffer byteBuffer = ByteBuffer.wrap(modifiedBytecode);

return defineClass(name, byteBuffer, null);
}
}

// 修改类
private byte[] modifyBytecode(byte[] originalBytecode) {
// 创建 ClassReader 和 ClassWriter
ClassReader classReader = new ClassReader(originalBytecode);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

// 创建自定义的 ClassVisitor
MyMethodLoggerClassVisitor myMethodLoggerClassVisitor = new MyMethodLoggerClassVisitor(classWriter);

// 使用 ClassReader 遍历字节码,应用自定义的 ClassVisitor
classReader.accept(myMethodLoggerClassVisitor, ClassReader.EXPAND_FRAMES);

// 返回修改后的字节码
return classWriter.toByteArray();
}

// 获取类的字节数据
private byte[] getClassData(String className) {
String path = classNameToPath(className);

try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bytesNumRead;
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];

while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

// 获取类路径
private String classNameToPath(String className) {
return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
}
}

这里编译完成后,把Foo类编译的 class 文件移动到E:\Temp

动态代理

动态代理是一种常用的设计模式,可以在运行时动态地为对象生成代理。使用 Java ASM,我们可以生成字节码来实现动态代理。以下是一个简单的动态代理示例,它实现了一个基于接口的代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;

public class MyDynamicProxy {

public static <T> T createProxy(Class<T> interfaceClass, InvocationHandler handler) {
// 创建 ClassWriter
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

// 定义代理类,实现指定的接口
String proxyClassName = interfaceClass.getName() + "$Proxy";
String proxyClassInternalName = proxyClassName.replace('.', '/');
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, proxyClassInternalName, null, "java/lang/Object", new String[]{Type.getInternalName(interfaceClass)});

// 实现代理类的构造方法
MethodVisitor constructorVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructorVisitor.visitCode();
constructorVisitor.visitVarInsn(Opcodes.ALOAD, 0);
constructorVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructorVisitor.visitInsn(Opcodes.RETURN);
constructorVisitor.visitMaxs(1, 1);
constructorVisitor.visitEnd();

// 实现接口的所有方法
for (Method method : interfaceClass.getDeclaredMethods()) {
String methodName = method.getName();
String methodDescriptor = Type.getMethodDescriptor(method);
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor, null, null);

// 在代理方法中调用 InvocationHandler 的 invoke 方法
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, proxyClassInternalName, "handler", "Ljava/lang/reflect/InvocationHandler;");
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitLdcInsn(Type.getType(interfaceClass));
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/reflect/Method", "valueOf", "(Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(Opcodes.ARETURN);
methodVisitor.visitMaxs(4, 2);
methodVisitor.visitEnd();
}

classWriter.visitEnd();

// 使用自定义类加载器加载代理类
byte[] proxyClassBytecode = classWriter.toByteArray();
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> proxyClass = myClassLoader.defineClass(proxyClassName, ByteBuffer.wrap(proxyClassBytecode));

// 创建代理类实例,并设置 InvocationHandler
try {
T proxyInstance = (T) proxyClass.getConstructor().newInstance();
proxyClass.getField("handler").set(proxyInstance, handler);
return proxyInstance;
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to create proxy instance", e);
}
}

static class MyClassLoader extends ClassLoader {
Class<?> defineClass(String name, java.nio.ByteBuffer b) {
return super.defineClass(name, b, null);
}
}
}

现在,我们可以使用 MyDynamicProxy 类为接口创建动态代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface MyInterface {
void doSomething();
}

// 另一个类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) {
MyInterface proxy = MyDynamicProxy.createProxy(MyInterface.class, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before doSomething");
// 调用原始对象的方法,如果需要
System.out.println("After doSomething");
return null;
}
});

proxy.doSomething();
}
}

这里我运行代码报错了,但 ASM 实现动态代理大概的思想如上面的代码所示,这里报错没找到原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Exception in thread "main" java.lang.VerifyError: Bad local variable type
Exception Details:
Location:
MyInterface$Proxy.doSomething()V @10: aload_1
Reason:
Type top (current frame, locals[1]) is not assignable to reference type
Current Frame:
bci: @10
flags: { }
locals: { 'MyInterface$Proxy' }
stack: { 'java/lang/reflect/InvocationHandler', 'MyInterface$Proxy', 'java/lang/reflect/Method' }
Bytecode:
0x0000000: 2ab4 000f 2a12 06b8 0015 2bb9 001b 0400
0x0000010: b0

at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getConstructor0(Class.java:3075)
at java.lang.Class.getConstructor(Class.java:1825)
at MyDynamicProxy.createProxy(MyDynamicProxy.java:59)
at Main.main(Main.java:6)

总结

Java ASM 是一个强大的字节码操作库,可以让我们在运行时修改和生成 Java 类。本文介绍了 Java ASM 的基础概念,如何使用它读取、修改和生成字节码,并通过实际案例学习了 Java ASM 的应用。还探讨了高级技巧,如实现自定义类加载器和动态代理

掌握 Java ASM 的技巧可以帮助您更好地理解 Java 字节码和虚拟机的工作原理,从而提高在性能优化、调试和工具开发等方面的能力。虽然在许多场景中,我们可以使用更高级的抽象和工具,如反射和动态代理,但了解底层字节码操作仍然具有很高的价值

需要注意的是,直接操作字节码可能会导致难以调试的问题,因此在实际项目中应谨慎使用。在使用 Java ASM 时,确保充分了解其潜在风险,并确保在修改字节码时保持对 Java 虚拟机规范的遵从性


本文参考链接:

万字长文,带你一脚踢开Java ASM字节码框架的大门!