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 操作字节码时,我们需要了解以下基本概念:
类:Java 字节码表示的是 Java 类,包括类的名称、修饰符、父类、接口、字段和方法等信息
方法:类中的方法由方法描述符、方法签名、返回值类型、参数类型、局部变量表和指令集等信息组成
字段:类中的字段包括字段名、字段描述符、字段签名、修饰符和初始值等信息
指令: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 = 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 { public MyClassVisitor () { super (Opcodes.ASM5); } @Override public void visit (int version, int access, String name, String signature, String superName, String[] interfaces) { System.out.println("Class: " + name); super .visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod (int access, String name, String descriptor, String signature, String[] exceptions) { System.out.println("Method: " + name); return super .visitMethod(access, name, descriptor, signature, exceptions); } }
visit()
方法参数说明:
version:类文件的版本号,表示类文件的 JDK 版本。例如,JDK1.8 对应的版本号为 52(0x34),JDK 11 对应的版本号为 55(0x37)
access:类访问标志,表示类的访问权限和属性。例如,ACC_PUBLIC(0x0001)表示类是公共的,ACC_FINAL(0x0010)表示类是 final 的。可以通过位运算组合多个访问标志
name:类的内部名称,用斜线代替点分隔包名和类名。例如com/example/MyClass
signature:类的泛型签名,如果类没有泛型信息,此参数为 null
superName:父类的内部名称。对于除java.lang.Object
之外的所有类,此参数都不为 null
interfaces:类实现的接口的内部名称数组。如果类没有实现任何接口,此参数为空数组
visitMethod()
方法参数说明:
access:方法访问标志,表示方法的访问权限和属性。例如,ACC_PUBLIC(0x0001)表示方法是公共的,ACC_STATIC(0x0008)表示方法是静态的。可以通过位运算组合多个访问标志
name:方法的名称。例如,doSomething
或<init>
(构造方法)
descriptor:方法的描述符,表示方法的参数类型和返回值类型。例如,对于方法void doSomething(int)
,描述符为(I)V
signature:方法的泛型签名,如果方法没有泛型信息,此参数为 null
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) { if ("toBeRemovedField" .equals(name)) { return null ; } return super .visitField(access, name, descriptor, signature, value); } @Override public void visitEnd () { FieldVisitor newFieldVisitor = super .visitField(Opcodes.ACC_PRIVATE, "newField" , "Ljava/lang/String;" , null , null ); if (newFieldVisitor != null ) { newFieldVisitor.visitEnd(); } super .visitEnd(); } }
visitField()
的方法参数说明:
access:字段访问标志,表示字段的访问权限和属性。例如,ACC_PUBLIC(0x0001)表示字段是公共的,ACC_STATIC(0x0008)表示字段是静态的。可以通过位运算组合多个访问标志
name:字段的名称。例如myField
descriptor:字段的描述符,表示字段的类型。例如,对于类型为 int 的字段,描述符为I
signature:字段的泛型签名,如果字段没有泛型信息,此参数为 null
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) { 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 = new ClassWriter (ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); 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, ClassReader.EXPAND_FRAMES); byte [] modifiedBytecode = classWriter.toByteArray(); 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 { byte [] modifiedBytecode = modifyBytecode(classData); ByteBuffer byteBuffer = ByteBuffer.wrap(modifiedBytecode); return defineClass(name, byteBuffer, null ); } } private byte [] modifyBytecode(byte [] originalBytecode) { ClassReader classReader = new ClassReader (originalBytecode); ClassWriter classWriter = new ClassWriter (ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); MyMethodLoggerClassVisitor myMethodLoggerClassVisitor = new MyMethodLoggerClassVisitor (classWriter); 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 = 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 ); 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)); 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字节码框架的大门!