1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > ASM学习笔记2 - 类的创建和修改 —— ClassWriter的综合应用

ASM学习笔记2 - 类的创建和修改 —— ClassWriter的综合应用

时间:2019-07-26 07:46:52

相关推荐

ASM学习笔记2 - 类的创建和修改 —— ClassWriter的综合应用

ASM学习笔记2 - 类的创建和修改 —— ClassWriter的综合应用

上回我们说到,通过使用ClassVisitor和ClassReader,我们能够分析已经存在的类。这一节中,我们将使用ClassWriter来创建和修改类。

1 ClassWriter

ClassWriter用于“编写”,即创建和修改类。注意ClassWriter继承了ClassVisitor

构造器

ClassWriter具有以下构造器:

其中,int为flag,为可选标志,可用于修改该类的默认行为,可以用于设置COMPUTE_MAXS和COMPUTE_FRAMES的值。这里我们设为0。也可使用ClassReader参数将其与ClassReader建立连接。

方法

ClassWriter提供了一类visit方法来编写类。使用visit方法开始编写,visitEnd方法结束编写。可以注意到,ClassWriter所提供的方法名与ClassVisitor一致,这意味着也可以使用ClassVisitor来操纵其行为。

具体的来说,此类visit方法能够为需要修改的类添加属性和方法。例如,visitMethod可以为类添加方法,visitAttribute可以为类添加属性。

完成一个visit周期,即类创建完成后,可使用toByteArray()将生成的类以Byte数组导出。

接下来是一些具体的方法解释,以下内容来源于ASM4.0文档:

visit

public final void visit(int version,int access,String name,String signature,String superName,String[] interfaces)

Description copied from class:ClassVisitor

Visits the header of the class.

Overrides:

visitin classClassVisitor

Parameters:

version- the class version.

access- the class’s access flags (seeOpcodes). This parameter also indicates if the class is deprecated.

name- the internal name of the class (seegetInternalName).

signature- the signature of this class. May benullif the class is not a generic one, and does not extend or implement generic classes or interfaces.

superName- the internal of name of the super class (seegetInternalName). For interfaces, the super class isObject. May benull, but only for theObjectclass.

interfaces- the internal names of the class’s interfaces (seegetInternalName). May benull.

version为编辑的类的java版本,例如V1_8;

access为访问标识,即该类的修饰,如ACC_PUBLIC. 若一个类具有多个修饰符,将Opcode码相加即可;

name为类的内部名;

signature为签名,可为null;

superName描述它的超类,即extends的类,填写内部名;

superName描述它的接口,即implements的类,填写内部名;

例:

ClassWriter cw = new ClassWriter(0);cw.visit(V1_8, ACC_PUBLIC,"clazz/MyClass", null, "java/lang/Object",null);

这将创建一个名为MyClass的自定类。我们将该类输出,查看结果:

ClassWriter cw = new ClassWriter(0);cw.visit(V1_8, ACC_PUBLIC,"clazz/MyClass", null, "java/lang/Object",null);cw.visitEnd();byte[] b = cw.toByteArray();FileOutputStream fos = new FileOutputStream(new File("src/clazz/MyClass.class"));fos.write(b);fos.close();

这段代码将在clazz包下生成MyClass.class。 经IDEA反编译结果如下:

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package clazz;public class MyClass {}

类创建成功。

插入变量和函数的过程类似,读者可自行尝试。方法解释如下:

visitField

public final FieldVisitor visitField(int access,String name,String desc,String signature,Object value)

Description copied from class:ClassVisitor

Visits a field of the class.

Overrides:

visitFieldin classClassVisitor

Parameters:

access- the field’s access flags (seeOpcodes). This parameter also indicates if the field is synthetic and/or deprecated.

name- the field’s name.

desc- the field’s descriptor (seeType).

signature- the field’s signature. May benullif the field’s type does not use generic types.

value- the field’s initial value. This parameter, which may benullif the field does not have an initial value, must be anInteger, aFloat, aLong, aDoubleor aString(forint,float,longorStringfields respectively).This parameter is only used for static fields. Its value is ignored for non static fields, which must be initialized through bytecode instructions in constructors or methods.

Returns:

a visitor to visit field annotations and attributes, ornullif this class visitor is not interested in visiting these annotations and attributes.

visitMethod

public final MethodVisitor visitMethod(int access,String name,String desc,String signature,String[] exceptions)

Description copied from class:ClassVisitor

Visits a method of the class. This methodmustreturn a newMethodVisitorinstance (ornull) each time it is called, i.e., it should not return a previously returned visitor.

Overrides:

visitMethodin classClassVisitor

Parameters:

access- the method’s access flags (seeOpcodes). This parameter also indicates if the method is synthetic and/or deprecated.

name- the method’s name.

desc- the method’s descriptor (seeType).

signature- the method’s signature. May benullif the method parameters, return type and exceptions do not use generic types.

exceptions- the internal names of the method’s exception classes (seegetInternalName). May benull.

Returns:

an object to visit the byte code of the method, ornullif this class visitor is not interested in visiting the code of this method.

注:descriptor即指描述符。

实例

下面给一个例子,生成一个带有print方法的TreeNode:

ClassWriter cw = new ClassWriter(0);cw.visit(V1_8, ACC_PUBLIC,"clazz/TreeNode", null, "java/lang/Object",null);cw.visitField(0,"left","Lclazz/TreeNode;",null,null);cw.visitField(0,"right","Lclazz/TreeNode;",null,null);cw.visitMethod(ACC_PUBLIC,"print","()V",null,null);cw.visitEnd();byte[] b = cw.toByteArray();FileOutputStream fos = new FileOutputStream(new File("src/clazz/TreeNode.class"));fos.write(b);fos.close();

输出类如下:

package clazz;public class TreeNode {TreeNode left;TreeNode right;public void print() {}}

我们尚未在print方法下写入代码。这需要使用MethodVisitor,在本篇中暂时不会提及。

2 综合使用

我们已经了解了如何使用ClassWriter来编写一个类。但如果需要进行修改类的操作,则需要与ClassReader和ClassVisitor一起使用。在这之前,我们来了解一下ClassVisitor的事件转发:

事件转发

我们知道,ClassVisitor还有一个构造器,传入两个参数,一个int类型的flag,还有一个ClassVisitor。我们通过下面的代码解释:

ClassVisitor cv1 = new ClassVisitor(ASM4) {};ClassVisitor cv2 = new ClassVisitor(ASM4,cv1) {};

其中,cv2使用了两个参数的构造器,实现了事件转发。即,当cv2开始处理事件时,cv2除了自己进行处理,还会将事件转发给cv1。我们来看一部分ClassVisitor的源码:

public abstract class ClassVisitor {protected final int api;protected ClassVisitor cv;public ClassVisitor(int var1, ClassVisitor var2) {if (var1 != 262144 && var1 != 327680) {throw new IllegalArgumentException();} else {this.api = var1;this.cv = var2;}}public MethodVisitor visitMethod(int var1, String var2, String var3, String var4, String[] var5) {return this.cv != null ? this.cv.visitMethod(var1, var2, var3, var4, var5) : null;}}

可以看到ClassVisitor内也具有一个ClassVisitor对象。若使用单参数构造器,则cv为null。当其调用visitMethod时,若cv不为null,则再调用cv的visitMethod方法。

那事件转发有什么用呢?

类的修改

我们知道,ClassWriter本身继承了ClassVisitor,在调用它的visit类型的方法时,它会操纵类,向内插入字节码。我们来看下面的代码会做些什么:

ClassWriter cw = new ClassWriter(0);ClassReader cr = new ClassReader("clazz/Hello");cr.accept(cw, 0);byte[] b = cw.toByteArray();

这段代码将cr与cw建立连接,cr读到什么内容,cw就跟着执行什么方法,也就编写什么内容。cr访问完所有内容后,cw就能输出和Hello类一模一样的类。这可能没有什么用,但是如果我们加一层中间层,可以做的就多了:

ClassWriter cw = new ClassWriter(0);ClassVisitor cv = new ClassVisitor(ASM4, cw) {};ClassReader cr = new ClassReader("clazz/Hello");cr.accept(cv, 0);byte[] b = cw.toByteArray();

这里我们添加了一个cv中间层,这里用到了事件转发。首先cr会触发cv的方法,cv再将事务转发给cw。这样做虽然对于这个例子依然没有什么意义,但是为我们提供了修改类的契机:我们可以重写ClassVisitor,修改它的事件转发,让我们人为的筛选需要修改的内容。我们看下面的例子:

现在我们有一个Hello类,代码如下:

public class Hello {static int a = 3;static int b = 5;static double c = 7.0;public Hello() {}public static void main(String[] args) {}}

假设这个Hello类非常复杂繁琐,根本无法手动修改。现在为了扩展使用,我们想修改这个类,将所有的int变量设为long变量。该如何做呢?下面是一个示例:

package clazz;import .objectweb.asm.ClassReader;import .objectweb.asm.ClassVisitor;import .objectweb.asm.ClassWriter;import .objectweb.asm.FieldVisitor;import static .objectweb.asm.Opcodes.*;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;public class Test2 {public static void main(String[] var0) throws IOException {ClassWriter cw = new ClassWriter(0);ClassVisitor cv = new MyClassVisitor(ASM4, cw) {};ClassReader cr = new ClassReader("clazz/Hello");cr.accept(cv, 0);byte[] b = cw.toByteArray();FileOutputStream fos = new FileOutputStream(new File("src/clazz/Hello.class"));fos.write(b);fos.close();}static class MyClassVisitor extends ClassVisitor {public MyClassVisitor(int i, ClassVisitor classVisitor) {super(i, classVisitor);}@Overridepublic FieldVisitor visitField(int i, String s, String s1, String s2, Object o) {if (s1.equals("I")) s1 = "J";return super.visitField(i, s, s1, s2, o);}}}

我们重写了visitField方法。当cv接收到visitField方法时,它会筛选出所有描述符为I(即int)的变量,将其描述符修改为J(即long)。这样将事件转发给ClassWriter后,cw就能据此修改class,修改就完成了。

输出的class文件如下:

package clazz;public class Hello {static long a;static long b;static double c;public Hello() {}public static void main(String[] args) {}static {a = 3;b = 5;c = 7.0D;}}

用类似的方法还能为类添加成员或删除成员。做法也很简单,如果想删除一个方法,就再其转发时返回null即可。若想在main方法后添加一个方法,则在读取main方法时额外访问一个visitMethod即可。推荐读者试一试下面的问题:

假设我们有一些类,这个类下有很多成员变量,然而他们都没有设置getter和setter方法。现在请你设计一个继承ClassVisitor的MyClassVisitor, 使用它来为这些类添加getter和setter方法。鉴于我们还没有学到MethodVisitor,添加的这些方法可以是空方法。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。