ASM基础 简单介绍 ASM 是一个通用的 Java 字节码操作和分析框架。它可直接以二进制形式用于修改现有类或动态生成类。应用场景有代码转换、优化、代码生成、动态字节码增强等。可以概括为generation、transformation 和 analysis
设计原理—访问者模式 访问者 是一种行为设计模式, 允许你在不修改已有代码的情况下向已有类层次结构中增加新的行为。
访问者模式结构:
我的理解 :
假设存在对象A和B,现在需要向AB对象分别添加一个look操作,该怎么做?
通常做法 :在AB对象加入一个look方法,但是这样会造成代码频繁改动
改进方法 :编写一个vistor,同时有两个方法,分别为
lookA(Element A)
lookB(Element B)
这样会存在一个问题,假设现在存在一个元素e,需要先进行类型判断才能够确定是调用lookA还是lookB
最终方法 :在AB对象初始设计的时候,加入一个accept方法,其传入的参数为visitor接口,函数体为调用接口中定义有关于A的方法,即lookA,这样在后面需要对对象进行如何操作时,只需要实现继承visitor接口的访问者类,并将操作写在lookA方法中
参考 :https://refactoringguru.cn/design-patterns/visitor/java/example
通常处理流程 目标类 class bytes->
ClassReader 解析->
ClassVisitor 增强修改字节码->
ClassWriter 生成增强后的 class bytes->
通过 Instrumentation 解析加载为新的 Class
环境 Maven依赖:
1 2 3 4 5 <dependency > <groupId > org.ow2.asm</groupId > <artifactId > asm</artifactId > <version > 9.3</version > </dependency >
常用类与方法 ClassVisitor 用于生成和转换已编译类的 ASM API 是基于 ClassVisitor 抽象类的,将它收到的所有方法调用都委托给另一个 ClassVisitor 类,会调用该类的visitXXX方法,这个类可以看作一个事件筛选器
方法访问顺序
1 2 3 visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField | visitMethod )* visitEnd
?表示最多一个,*表示任意个
相关方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public abstract class ClassVisitor { public ClassVisitor (int api) ; public ClassVisitor (int api, ClassVisitor cv) ; public void visit (int version, int access, String name, String signature, String superName, String[] interfaces) ; public void visitSource (String source, String debug) ; public void visitOuterClass (String owner, String name, String desc) ; AnnotationVisitor visitAnnotation (String desc, boolean visible) ; public void visitAttribute (Attribute attr) ; public void visitInnerClass (String name, String outerName, String innerName, int access) ; public FieldVisitor visitField (int access, String name, String desc, String signature, Object value) ; public MethodVisitor visitMethod (int access, String name, String desc, String signature, String[] exceptions) ; void visitEnd () ; }
ClassReader 该类解析 ClassFile 内容,并针对遇到的每个字段、方法和字节码指令调用给定 ClassVisitor 的相应访问方法。这个类可以看作一个事件生产者
构造方法
1 2 public ClassReader (byte [] classFile)
方法
1 2 3 public void accept (ClassVisitor classVisitor, int parsingOptions)
ClassWriter ClassWriter 类是 ClassVisitor 抽象类的一个子类,它直接以二进制形式生成编译后的类。它会生成一个字节数组形式的输出,其中包含了已编译类,可以用 toByteArray 方法来提取。这个类可以看作一个事件消费者
构造方法
1 2 3 4 public ClassWriter (int flags) public ClassWriter (ClassReader classReader, int flags)
方法
1 2 3 4 public byte [] toByteArray()
MethodVisitor 访问Java方法的访问者类,用于生成和转换已编译方法的 ASM API 是基于 MethodVisitor 抽象类的,它由 ClassVisitor 的 visitMethod 方法返回。
方法访问顺序
1 2 3 4 5 6 visitAnnotationDefault? ( visitAnnotation | visitParameterAnnotation | visitAttribute )* ( visitCode ( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn | visitLocalVariable | visitLineNumber )* visitMaxs )? visitEnd
对非抽象方法,如果存在注解和属性,必须先访问;其次是按顺序访问字节代码,这些访问在visitCode与visitMaxs之间
相关方法
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 abstract class MethodVisitor { MethodVisitor(int api); MethodVisitor(int api, MethodVisitor mv); AnnotationVisitor visitAnnotationDefault () ; AnnotationVisitor visitAnnotation (String desc, boolean visible) ; AnnotationVisitor visitParameterAnnotation (int parameter, String desc, boolean visible) ; void visitAttribute (Attribute attr) ; void visitCode () ; void visitFrame (int type, int nLocal, Object[] local, int nStack, Object[] stack) ; void visitInsn (int opcode) ; void visitIntInsn (int opcode, int operand) ; void visitVarInsn (int opcode, int var ) ; void visitTypeInsn (int opcode, String desc) ; void visitFieldInsn (int opc, String owner, String name, String desc) ; void visitMethodInsn (int opc, String owner, String name, String desc) ; void visitInvokeDynamicInsn (String name, String desc, Handle bsm, Object... bsmArgs) ; void visitJumpInsn (int opcode, Label label) ; void visitLabel (Label label) ; void visitLdcInsn (Object cst) ; void visitIincInsn (int var , int increment) ; void visitTableSwitchInsn (int min, int max, Label dflt, Label[] labels) ; void visitLookupSwitchInsn (Label dflt, int [] keys, Label[] labels) ; void visitMultiANewArrayInsn (String desc, int dims) ; void visitTryCatchBlock (Label start, Label end, Label handler, String type) ; void visitLocalVariable (String name, String desc, String signature, Label start, Label end, int index) ; void visitLineNumber (int line, Label start) ; void visitMaxs (int maxStack, int maxLocals) ; void visitEnd () ; }
修改方法的步骤:原始方法和修改后的方法编译后进行对比,在通过visit操作进行修改
解析字节码 ClassReader加载字节码 该类解析 ClassFile 内容,并针对遇到的每个字段、方法和字节码指令调用给定 ClassVisitor 的相应访问方法。
构造方法
1 2 public ClassReader (byte [] classFile)
方法
1 2 3 public void accept (ClassVisitor classVisitor, int parsingOptions)
实例
1 2 3 4 5 6 7 8 9 10 11 byte [] bytecode = Files.readAllBytes(Paths.get("path/to/MyClass.class" )); FileInputStream bytecode = new FileInputStream ("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 protected ClassVisitor (int api) protected ClassVisitor (int api, ClassVisitor classVisitor)
方法
1 2 public void visit (int version, int access, String name, String signature, String superName, String[] interfaces)
version:表示类文件的 JDK 版本
access:表示类的访问权限和属性
name:类的内部名称,用斜线代替点分隔包名和类名
signature:类的泛型签名,如果类没有泛型信息,此参数为 null
superName:父类的内部名称
interfaces:类实现的接口的内部名称数组。如果类没有实现任何接口,此参数为空数组
实例
实验类:bytecodeTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package bytecode;public class bytecodeTest extends Person implements helloInterface { private String sex; public bytecodeTest (String name, int age, String sex) { super (name, age); this .sex = sex; } public void sayHello () { System.out.println("Hello" + super .name); } }
访问者类:MyClassVisitor
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 package javaasm;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("The class name:" + 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("The method name:" + name); return super .visitMethod(access, name, descriptor, signature, exceptions); } }
测试类:Main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package javaasm;import org.objectweb.asm.ClassReader;import java.io.FileInputStream;public class Main { public static void main (String[] args) throws Exception{ MyClassVisitor myClassVisitor = new MyClassVisitor (); FileInputStream stream = new FileInputStream ("target/classes/bytecode/bytecodeTest.class" ); ClassReader reader = new ClassReader (stream); reader.accept(myClassVisitor, 0 ); } }
结果:
1 2 3 4 5 The class name :bytecode/bytecodeTest The method name:<init> The method name:sayHello Process finished with exit code 0
修改字节码 添加与删除Field 实例
删除sex属性,增加address属性
实验类:bytecodeTest
访问者类:UpdateFieldClassVisitor
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 package javaasm;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.FieldVisitor;import org.objectweb.asm.Opcodes;public class UpdateFieldClassVisitor extends ClassVisitor { private String deleteFieldName; private int addFieldAcc; private String addFieldName; private String addFieldDesc; private Boolean flag = false ; protected UpdateFieldClassVisitor (ClassVisitor cv, String deleteFieldName, int addFieldAcc, String addFieldName, String addFieldDesc) { super (Opcodes.ASM5, cv); this .deleteFieldName = deleteFieldName; this .addFieldAcc = addFieldAcc; this .addFieldName = addFieldName; this .addFieldDesc = addFieldDesc; } @Override public FieldVisitor visitField (int access, String name, String descriptor, String signature, Object value) { if (name.equals(deleteFieldName)) { return null ; } if (name.equals(addFieldName)) flag = true ; return super .visitField(access, name, descriptor, signature, value); } @Override public void visitEnd () { if (!flag) { FieldVisitor fieldVisitor = super .visitField(addFieldAcc, addFieldName, addFieldDesc, null , null ); if (fieldVisitor != null ) { fieldVisitor.visitEnd(); } } super .visitEnd(); } }
测试类:Main
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 package javaasm;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.Opcodes;import java.io.FileInputStream;import java.io.FileOutputStream;public class Main { public static void main (String[] args) throws Exception{ FileInputStream stream = new FileInputStream ("target/classes/bytecode/bytecodeTest.class" ); ClassReader reader = new ClassReader (stream); ClassWriter writer = new ClassWriter (reader, ClassWriter.COMPUTE_FRAMES); UpdateFieldClassVisitor updateFieldClassVisitor = new UpdateFieldClassVisitor (writer, "sex" , Opcodes.ACC_PRIVATE, "address" , "Ljava/lang/String;" ); reader.accept(updateFieldClassVisitor, ClassReader.EXPAND_FRAMES); FileOutputStream fileOutputStream = new FileOutputStream ("temp.class" ); byte [] updateByte = writer.toByteArray(); fileOutputStream.write(updateByte); fileOutputStream.close(); ClassReader classReader = new ClassReader (updateByte); MyClassVisitor myClassVisitor = new MyClassVisitor (); classReader.accept(myClassVisitor, 0 ); } }
结果:
1 2 3 4 5 6 The class name :bytecode/bytecodeTest The field name:address The method name:<init> The method name:sayHello Process finished with exit code 0
添加与删除Method 实例
删除sayHello方法,增加newMethod方法
实验类:bytecodeTest
访问者类:UpdateMethodClassVisitor
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 package javaasm;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Opcodes;public class UpdateMethodClassVisitor extends ClassVisitor { private String deleteMethodName; private String deleteMethodDesc; private int addMethodAcc; private String addMethodName; private String addMethodDesc; private boolean flag = false ; protected UpdateMethodClassVisitor (ClassVisitor cv, String deleteMethodName, String deleteMethodDesc, int addMethodAcc, String addMethodName, String addMethodDesc) { super (Opcodes.ASM5, cv); this .deleteMethodName = deleteMethodName; this .deleteMethodDesc = deleteMethodDesc; this .addMethodAcc = addMethodAcc; this .addMethodName = addMethodName; this .addMethodDesc = addMethodDesc; } @Override public MethodVisitor visitMethod (int access, String name, String descriptor, String signature, String[] exceptions) { if (name.equals(deleteMethodName) && descriptor.equals(deleteMethodDesc)) { return null ; } if (name.equals(addMethodName) && descriptor.equals(addMethodDesc)) flag = true ; return super .visitMethod(access, name, descriptor, signature, exceptions); } @Override public void visitEnd () { if (!flag) { MethodVisitor methodVisitor = super .visitMethod(addMethodAcc, addMethodName, addMethodDesc, null , null ); if (methodVisitor != null ) { methodVisitor.visitCode(); methodVisitor.visitInsn(Opcodes.RETURN); methodVisitor.visitMaxs(0 , 0 ); methodVisitor.visitEnd(); } } super .visitEnd(); } }
测试类Main
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 package javaasm;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.Opcodes;import java.io.FileInputStream;import java.io.FileOutputStream;public class Main { public static void main (String[] args) throws Exception{ FileInputStream stream = new FileInputStream ("target/classes/bytecode/bytecodeTest.class" ); ClassReader reader = new ClassReader (stream); ClassWriter writer = new ClassWriter (reader, ClassWriter.COMPUTE_FRAMES); UpdateMethodClassVisitor updateMethodClassVisitor = new UpdateMethodClassVisitor (writer, "sayHello" , "()V" , Opcodes.ACC_PUBLIC, "newMethod" , "()V" ); reader.accept(updateMethodClassVisitor, ClassReader.EXPAND_FRAMES); FileOutputStream fileOutputStream = new FileOutputStream ("temp.class" ); byte [] updateByte = writer.toByteArray(); fileOutputStream.write(updateByte); fileOutputStream.close(); ClassReader classReader = new ClassReader (updateByte); MyClassVisitor myClassVisitor = new MyClassVisitor (); classReader.accept(myClassVisitor, 0 ); } }
结果
1 2 3 4 5 6 The class name :bytecode/bytecodeTest The field name:sex The method name:<init> The method name:newMethod Process finished with exit code 0
修改方法指令 实例
在方法开头加入输出语句
实验类:bytecodeTest
方法适配器:ModMethodAdapter
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 package javaasm;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Opcodes;public class ModMethodAdapter extends MethodVisitor { public ModMethodAdapter (MethodVisitor methodVisitor) { super (Opcodes.ASM5, methodVisitor); } @Override public void visitCode () { mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System" , "out" , "Ljava/io/PrintStream;" ); mv.visitLdcInsn("Hello, World!" ); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream" , "println" , "(Ljava/lang/String;)V" , false ); super .visitCode(); } }
访问者类:ModMethodVisitor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package javaasm;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Opcodes;public class ModMethodVisitor extends ClassVisitor { protected ModMethodVisitor (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 ModMethodAdapter (methodVisitor); } }
测试类:Main
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 package javaasm;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassWriter;import org.objectweb.asm.Opcodes;import java.io.FileInputStream;import java.io.FileOutputStream;public class Main { public static void main (String[] args) throws Exception{ FileInputStream stream = new FileInputStream ("target/classes/bytecode/bytecodeTest.class" ); ClassReader reader = new ClassReader (stream); ClassWriter writer = new ClassWriter (reader, ClassWriter.COMPUTE_FRAMES); ModMethodVisitor modMethodVisitor = new ModMethodVisitor (writer); reader.accept(modMethodVisitor, ClassReader.EXPAND_FRAMES); FileOutputStream fileOutputStream = new FileOutputStream ("temp.class" ); byte [] updateByte = writer.toByteArray(); fileOutputStream.write(updateByte); fileOutputStream.close(); ClassReader classReader = new ClassReader (updateByte); MyClassVisitor myClassVisitor = new MyClassVisitor (); classReader.accept(myClassVisitor, 0 ); } }
参考 ASM 4.0 A Java bytecode engineering library (ow2.io)
org.objectweb.asm (ASM 9.6) (ow2.io)
Java ASM | Paoka1’s Blog
JAVA安全|字节码篇:字节码操作框架-ASM(基本使用)
ASM (ow2.io)
[Java ASM详解:ASM库使用 | Nickid2018的博客](https://nickid2018.github.io/2021/02/13/Java ASM详解:ASM库使用/)
lsieun/learn-java-asm: :bug: Java ASM (github.com)
注:本文首发于https://xz.aliyun.com/t/13334