2.3 ASM-类-工具类

时间:2022-06-06
本文章向大家介绍2.3 ASM-类-工具类,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

2.3 工具类

除了ClassVisitor类,以及相关的ClassReaderClassWriter等组件, ASM在org.objectweb.asm.util还提供了一些工具类,对开发一个Class生成器和适配器非常有用,但在程序运行中不是必需的。 ASM同样也提供了一个在程序运行时,处理内部名、类型描述符和方法描述符的工具类。 所有工具都会在下面进行介绍。

2.3.1 类型

正如前面章节介绍的,ASM的API展示Java类型,像编译后的class中一样,即使用内部名、类型描述符。 使用源码中的展示方式,可以使代码更加易读。 但需要在ClassReaderClassWriter中系统的转换,这样会降低性能。 这就是为什么ASM不透明的将内部名和类型描述符转换成源码中的格式。 但是ASM提供了一个Type类,方便在需要的时候进行手动转换。

一个Type对象代表一种Java类型,可以由类型描述符或者Class对象创建。 Type类中也包含了一些表示基本数据类型的静态变量。 例如Type.INT_TYPE代表了int类型的Type对象。

getInternalName方法会返回一个Type对象的內部名。 例如,Type.getType(String.class).getInternalName()会返回String类的内部名,即“java/lang/String”。 这个方法只能是类(class)或者接口(interface)类型调用。

getDescriptor方法会返回一个Type对象的类型描述符。 例如,相比于在代码中使用“Ljava/lang/String;”,可以使用“Type.getType(String.class).getDescriptor()”代替。 再者,相比于使用“I”,可以使用“Type.INT_TYPE.getDescriptor()”

一个Type对象也可以表示一个方法类型。这些对象可以由方法描述符或者一个方法对象创建。 getDescriptor方法会返回该类型相对应的方法描述符。 除此以外,getArgumentTypes方法和getReturnType方法可以返回Type对象对应的参数类型和方法返回值类型。 例如,Type.getArgumentTypes(“(I)V”) 会返回包含一个Type.INT_TYPE元素的数组。 同样的,调用Type.getReturnType(“(I)V”)会返回一个Type.VOID_TYPE对象。

2.3.2. TraceClassVisitor

为了检查生成或者转换后的class是否是你所期望的,通过byte数组不会有所帮助,因为byte数组可读性太差。 使用文本表述会更容易阅读。这就是TraceClassVisitor所提供的功能。 顾名思义,这个类继承了ClassVisitor,为访问的class创建一个易读的文本描述。 这样,为了得到实际生成类可读性的描述,可以使用TraceClassVisitor代替ClassWriter。 或者,更胜一筹的是同时使用这两个类。 TraceClassVisitor除了默认的行为外,还可以委托所有调用它的方法到其他visitor上面,例如到一个ClassWriter

ClassWriter cw = new ClassWriter(0);
TraceClassVisitor cv = new TraceClassVisitor(cw, printWriter);
cv.visit(...);
...
cv.visitEnd();
byte b[] = cw.toByteArray();

上面的代码创建了一个TraceClassVisitor代理所有对它的调用到cw上面,并且将所有的调用都以文本的方式输出到printWriter中。 例如,在2.2.3章中使用TraceClassVisitor,将会得到如下的输出:

// class version 49.0 (49)
// access flags 1537
public abstract interface pkg/Comparable implements pkg/Mesurable {
    // access flags 25
    public final static I LESS=-1
    // access flags 25
    public final static I EQUAL=0
    // access flags 25
    public final static I GREATER=1
    // access flags 1025
    public abstract compareTo(Ljava/lang/Object;)I
}

需要注意的是,为了跟踪链路中的情况,你可以在生成或者转换链的任何一个节点使用TraceClassVisitor,但需要在ClassWriter之前。 还需要注意的是,本章生成的class文本描述可以使用String.equals()进行比较。

2.3.3. CheckClassAdapter

ClassWriter不会检查方法调用的顺序是否合适,以及参数是否合法。 因此生成被Java虚拟机验证器所拒绝的非法class是可行的。 为了尽快的检测到这些错误,可以使用CheckClassAdapter类。 和TraceClassVisitor一样,该类也继承了ClassVisitor类,并且代理所有调用转发到其他ClassVisitor,比如转发给TraceClassVisitor对象或者ClassWriter对象。 然而,不同于打印被访问class的文本描述信息,在将调用转发到下一个visitor之前,该类会先检查方法调用顺序是否合适,以及参数是否合法, 如果有错误,会抛出一个IllegalStateException或者IllegalArgumentException异常对象。 为了检测class、打印文本描述,并且最终输出byte数组,你可以使用下面这种方式:

ClassWriter cw = new ClassWriter(0);
TraceClassVisitor tcv = new TraceClassVisitor(cw, printWriter);
CheckClassAdapter cv = new CheckClassAdapter(tcv);
cv.visit(...);
...
cv.visitEnd();
byte b[] = cw.toByteArray();

需要注意的是,如果这些class的visitor处在不同的调用链中,这些操作所执行的顺序也会不一样。 例如下面代码所示,检测的vistor会在跟踪的visitor之后执行:

ClassWriter cw = new ClassWriter(0);
CheckClassAdapter cca = new CheckClassAdapter(cw);
TraceClassVisitor cv = new TraceClassVisitor(cca, printWriter);

TraceClassVisitor一样,为了检测节点中class的正确性,可以在生成或者转换调用链中的任意一点使用CheckClassAdapter,但顺序必须在ClassWriter之前:

2.3.4 ASMifier

ASMifier提供了一个代替TraceClassVisitor的后端调用(默认情况下使用一个Textifier,产生如上面所示文本描述)。 这个后端会根据TraceClassVisitor类所调用的每一个方法,打印出生成该方法的Java代码。 例如调用visitEnd()方法会打印‘cv.visitEnd();’。 产生的结果是,在后端调用ASMifier访问一个class的时候,就会打印出使用ASM构造该class的代码。 使用这个visitor访问编译后的class是非常有用的。 例如,如果不知道如何使用ASM生成一个编译后的class,你可以直接编写该类的源码,使用javac编译,最后使用ASMifier访问编译后的class。 就可以得到该编译类使用ASM生成的代码了。 ASMifier可以直接在命令行中使用,例如:

java -classpath asm.jar:asm-util.jar org.objectweb.asm.util.ASMifier java.lang.Runnable

生成的代码,使用缩进后,如下:

package asm.java.lang;
import org.objectweb.asm.*;
public class RunnableDump implements Opcodes {
    public static byte[] dump() throws Exception {
        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;
        cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
            "java/lang/Runnable", null, "java/lang/Object", null);
        mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "run", "()V", null, null);
        mv.visitEnd();
        cw.visitEnd();

        return cw.toByteArray();
    }
}