java asm删除方法_asm操作字节码,删除类的成员变量

本文详细介绍了ASM框架在Java字节码操作中的应用,包括如何修改class文件的字段和方法。ASM通过构建class文件的抽象语法树,并使用visitor模式进行修改。举例展示了如何删除、修改和添加成员变量及方法,为理解ASM提供了一个清晰的实践指南。
摘要由CSDN通过智能技术生成

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

打开可以看到其类库的内容:

qfHRb3fWBjW3e20XQLBBwAAAFByEHwAAAAAJQfBBwAAAFByEHwAAAAAJQfBBwAAAFBycgXfchsGAAAAAJ0hLPgAAAAAoDQg+AAAAABKDoIPAAAAoOQg+AAAAABKDoIPAAAAoOQg+AAAAABKDoIPAAAAoOQg+AAAAABKDoIPAAAAoOQg+AAAAABKDoIPAAAAoOQg+AAAAABKzv8PfAPbEPVmuv8AAAAASUVORK5CYII=

其第一个包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的官方文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值