https://blog.csdn.net/fyyyr/article/details/102816064
ASM基础
ASM是一个Java字节码操作框架,可用于class文件的修改。
其原理是将class文件载入,然后构建成一棵树。然后根据用户自定义的修改类对该树进行加工,加工完成后即可得到修改后的class文件。
故而ASM中使用了visitor模式:class文件的结构是固定的,根据其构造出的树作为被访问者,则其节点也是固定的。只需要对每个节点定义一个访问者即可进行指定的修改。
由于修改class主要涉及字段和方法,故最常用的visitor是FieldVisitor
和MethodVisitor
。
以FieldVisitor
为例,当ASM使用FieldVisitor
来处理一个class的树时,则该class的每个方法都会被传入定义的FieldVisitor
。于是只需要在FieldVisitor
对特定的方法进行过滤处理即可。
ASM的官方文档地址为:
https://asm.ow2.io/javadoc/overview-summary.html
打开可以看到其类库的内容:
其第一个包org.objectweb.asm
为核心core包,包含了主要的功能接口和对象。
环境搭建
ASM使用的是com.sun.xml.internal.ws.org.objectweb.asm
,因此不需要额外引入库文件。
创建一个JBoss工程,载入class并修改,然后将修改后的class文件进行保存:
import com.sun.xml.internal.ws.org.objectweb.asm.ClassAdapter; import com.sun.xml.internal.ws.org.objectweb.asm.ClassReader; import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter; import java.io.*; public class Main { public static void main(String[] args) throws Exception { // 载入class文件 FileInputStream fis = new FileInputStream("D:\\Test\\Hello.class"); // 修改 ClassReader cr = new ClassReader(fis); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new ASMTest(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); // 保存class文件 FileOutputStream fos = new FileOutputStream("D:\\Test\\Change\\Hello.class"); fos.write(cw.toByteArray()); fos.close(); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
这就是总体的框架。至于修改,由ASMTest
类负责。
新建一个Java类ASMTest
:
import com.sun.xml.internal.ws.org.objectweb.asm.*; public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } // 字段处理 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { return this.cv.visitField(access, name, desc, signature, value); } // 方法处理 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return this.cv.visitMethod(access, name, desc, signature, exceptions); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
上面定义了负责进行修改的类ASMTest
。直接运行main()
,会在目标路径下生成一个class。由于ASMTest
没有进行任何修改,故而生成的class与原始class内容相同。
解析
ClassReader
ClassReader
能够处理class文件的字节码数据,构建出一棵该类的抽象树。然后执行传入的参数对象所包含的操作,从而对抽象树进行加工。
ClassAdapter
ClassAdapter
继承自ClassVisitor
,负责对class树进行修改,开发者需要对其继承并重载对应的修改方法。
也就是说,所有的visitor都集成在了ClassAdapter
的方法中,只需要顺序调用ClassAdapter
的所有方法,即可实现所有visitor顺序访问class树。ClassAdapter
的定义为:
public class ClassAdapter implements ClassVisitor { protected ClassVisitor cv; public ClassAdapter(ClassVisitor cv) { this.cv = cv; } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.cv.visit(version, access, name, signature, superName, interfaces); } public void visitSource(String source, String debug) { this.cv.visitSource(source, debug); } public void visitOuterClass(String owner, String name, String desc) { this.cv.visitOuterClass(owner, name, desc); } public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return this.cv.visitAnnotation(desc, visible); } public void visitAttribute(Attribute attr) { this.cv.visitAttribute(attr); } public void visitInnerClass(String name, String outerName, String innerName, int access) { this.cv.visitInnerClass(name, outerName, innerName, access); } public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { return this.cv.visitField(access, name, desc, signature, value); } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return this.cv.visitMethod(access, name, desc, signature, exceptions); } public void visitEnd() { this.cv.visitEnd(); } }
- 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
其所有方法都可被重载。前面的例子中,ASMTest
重载了visitField()
和visitMethod()
,从而对字段和方法进行修改。ClassAdapter
的所有方法是按已安排好的顺序来调用的,也就是ClassAdapter
所有方法的定义顺序。这一点通常不需要开发者关心。
关于每个方法的具体说明,可参考其父类ClassVisitor
的文档:
https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html
修改
以前面ASMTest
为例。
设Hello.class的实现为:
public class Hello { String words = "Hello world!"; int value = 1; public void say() { System.out.println(words); } public void think() { words = "new thought"; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
变量
变量有个descriptor
属性,即描述符,是个字符串,每种类型都对应一个字符串:
java类型 | descriptor |
---|---|
boolean | Z |
char | C |
byte | B |
short | S |
int | I |
float | F |
long | J |
double | D |
Object | Ljava/lang/Object; |
int[] | [I |
Object[][] | [[Ljava/lang/Object; |
删除成员变量
删除Hello.class中的变量value
。
public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public FieldVisitor visitField(final int access, final String name,final String desc, final String signature, final Object value) { if (name.equals("value")) { return null; } return cv.visitField(access, name, desc, signature, value); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
Hello.class的所有成员变量会依次传入visitField()
。当传入的变量名为value
时,直接return null;
,会将该变量从class树中删除。
修改成员变量权限
修改Hello.class中的变量value
权限为public
。
public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public FieldVisitor visitField(final int access, final String name,final String desc, final String signature, final Object value) { if (name.equals("value")) { return cv.visitField(Opcodes.ACC_PUBLIC, name, desc, signature, value); } return cv.visitField(access, name, desc, signature, value); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
添加成员变量
为Hello.class添加int变量tInt
,其初始值为3。
public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public void visitEnd() { FieldVisitor fv = this.cv.visitField(Opcodes.ACC_PRIVATE, "temp", "I", null, 3); if (fv!=null){ fv.visitEnd(); } super.visitEnd(); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
要点:
- 在所有字段都访问完成后,会调用
ClassVisitor.visitEnd()
作为结束,此时即可进行成员变量的添加。 - 调用
visitField()
来访问成员变量。若要访问的成员变量不存在,则创建。
visitField()
的定义为:
public FieldVisitor visitField(int access, java.lang.String name, java.lang.String descriptor, java.lang.String signature, java.lang.Object value)
- 1
- 2
- 3
- 4
- 5
其中:
- descriptor: 类型描述符,即该成员变量的类型。
- signature: 签名。默认null即可。
- value: 初始值。
方法
方法的描述符descriptor
是个字符串,包含:
java类型 | descriptor |
---|---|
void m(int i, float) | (IF)V |
int m(Object o) | (Ljava/lang/Object;)I |
int[] m(int i, String s) | (ILjava/lang/String;)[I |
Object m(int[] i) | ([I)Ljava/lang/Object; |
包含两部分:
()
内的是参数类型,多个参数直接拼接即可。例如(IF)
,指有2个参数,第一个是int
,第二个是float
。()
后的是返回值类型。
删除方法
删除Hello.class中的方法think
。
public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.equals("think")) { return null; } return this.cv.visitMethod(access, name, desc, signature, exceptions); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
Hello.class的所有方法会依次传入visitMethod()
。当传入的方法名为think
时,直接return null;
,会将该方法从class树中删除。
上面这种写法是使用方法名来进行判断。然而,有些类有多个同名方法,使用上面的写法会将所有同名方法都删除。若要只删除某个方法,则需要同时对descriptor
进行判断:
if (name.equals("think") && desc.equals("I")) { return null; }
- 1
- 2
- 3
修改/添加
方法的修改/添加较为复杂。对于修改,常规做法是拦截到目标方法后,返回一个新的MethodVisitor
:
public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.equals("think")) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions); return new NewMethodAdapter(mv); } return this.cv.visitMethod(access, name, desc, signature, exceptions); } } class NewMethodAdapter extends MethodAdapter { public NewMethodAdapter(MethodVisitor mv) { super(mv); } public void visitCode() {} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
MethodAdapter
继承自MethodVisitor
。通过重载MethodAdapter
的各个方法来实现对类方法的修改。可参考MethodVisitor
的官方文档。