# 2.3 工具

除了 ClassVisitor 类和相关的 ClassReaderClassWriter 组件之外,ASM 还在 org.objectweb.asm.util 包中提供了几个工具,这些工具在开发类生成器或适配器时可能非常有用,但在运行时不需要它们。ASM 还提供了一个实用类,用于在运行时处理内部名、类型描述符和方法描述符。所有这些工具都将在下面介绍。

# 2.3.1 Type

在前几节已经看到,ASM API 公开 Java 类型的形式就是它们在已编译类中的存储形式,也就是说,作为内部特性或类型描述符。也可以按照它们在源代码中的形式来公开它们,使代码更便于阅读。但这样就需要在 ClassReaderClassWriter 中的两种表示形式之间进行系统转换,从而使性能降低。这就是为什么 ASM 没有透明地将内部名和类型描述符转换为它们等价的源代码形式。但它提供了 Type 类,可以在必要时进行手动转换。

一个 Type 对象表示一种 Java 类型,既可以由类型描述符构造,也可以由 Class 对象构建。 Type 类还包含表示基元类型的静态变量。例如,Type.INT_TYPE 是表示 int 类型的 Type 对象。

getInternalName 方法返回一个 Type 的内部名。 例如, Type.getType(String.class). getInternalName() 给出 String 类的内部名,即 "java/lang/String"。这一方法只能对类或接口类型使用。

getDescriptor 方法返回一个 Type 的描述符。 比如,在代码中可以不使用 "Ljava/lang/String;",而是使用Type.getType(String.class).getDescriptor()。或者,可以不使用 I,而是使用 Type.INT_TYPE.getDescriptor()

Type 对象还可以表示方法类型。这种对象既可以从一个方法描述符构建,也可以由 Method 对象构建。 getDescriptor 方法返回与这一类型对应的方法描述符。此外, getArgumentTypesgetReturnType 方法可用于获取与一个方法的参数类型和返回类型相对应的 Type 对象。例如,Type.getArgumentTypes("(I)V")返回一个仅有一个元素 Type.INT_TYPE 的数组。与此类似,调用 Type.getReturnType("(I)V") 将返回 Type.VOID_TYPE 对象。

# 2.3.2 TraceClassVisitor

要确认所生成或转换后的类符合你的预期,ClassWriter 返回的字母数组并没有什么真正的用处,因为它对人类来说是不可读的。如果有文本表示形式,那使用起来就容易多了。这正是 TraceClassVisitor 类提供的东西。从名字可以看出,这个类扩展了 ClassVisitor 类, 并生成所访问类的文本表示。因此,我们不是用 ClassWriter 来生成类,而是使用 TraceClassVisitor,以获得关于实际所生成内容的一个可读轨迹。甚至可以同时使用这两者,这样要更好一些。除了其默认行为之外,TraceClassVisitor 实际上还可以将对其方法的所有调用委托给另一个访问器,比如 ClassWriter

ClassWriter cw = new ClassWriter(0);
TraceClassVisitor cv = new TraceClassVisitor(cw, printWriter); 
cv.visit(...);
...
cv.visitEnd();
byte b[] = cw.toByteArray();
1
2
3
4
5
6

这一代码创建了一个 TraceClassVisitor,将它自己接收到的所有调用都委托给 cw,然后将这些调用的一份文本表示打印到 printWriter。例如,如果在 2.2.3 节的例子中使用 TraceClassVisitor,将会得出:

// 类版本号 49.0 (49)
// 访问标志 1537
public abstract interface pkg/Comparable implements pkg/Mesurable {
    // 访问标志 25
    public final static I LESS = -1
    
    //访问标志 25
    public final static I EQUAL = 0
    //访问标志 25
    public final static I GREATER = 1
    //访问标志 1025
    public abstract compareTo(Ljava/lang/Object;)I
}
1
2
3
4
5
6
7
8
9
10
11
12
13

注意,可以在生成链或转换链的任意位置使用 TraceClassVisitor,以查看在链中这一点发生了什么,并非一定要恰好在 ClassWriter 之前使用。还要注意,有了这个适配器生成的类的文本表示形式,可能很轻松地用 String.equals() 来对比两个类。

# 2.3.3 CheckClassAdapter

ClassWriter 类并不会核实对其方法的调用顺序是否恰当,以及参数是否有效。因此,有可能会生成一些被 Java 虚拟机验证器拒绝的无效类。为了尽可能提前检测出部分此类错误,可以使用 CheckClassAdapter 类。和 TraceClassVisitor 类似,这个类也扩展了 ClassVisitor 类,并将对其方法的所有调用都委托到另一个 ClassVisitor,比如一个 TraceClassVisitor 或一个 ClassWriter。但是,这个类并不会打印所访问类的文本表示, 而是验证其对方法的调用顺序是否适当,参数是否有效,然后才会委托给下一个访问器。当发生错误时,会抛出 IllegalStateExceptionIllegalArgumentException

为核对一个类,打印这个类的文本表示形式,最终创建一个字节数组表示形式,应当使用类似于如下代码:

ClassWriter cw = new ClassWriter(0);
TraceClassVisitor tcv = new TraceClassVisitor(cw, printWriter); 
CheckClassAdapter cv = new CheckClassAdapter(tcv); cv.visit(...);
...
cv.visitEnd();
byte b[] = cw.toByteArray();
1
2
3
4
5
6

注意,如果以不同顺序将这些类访问器链在一起,那它们执行的操作也将以不同顺序完成。例如,利用以下代码,这些核对工作将在轨迹之后进行:

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

和使用 TraceClassVisitor 时一样,也可以在一个生成链或转换链的任意位置使用 CheckClassAdapter,以查看该链中这一点的类,而不一定只是恰好在 ClassWriter 之前使用。

# 2.3.4 ASMifier

这个类为 TraceClassVisitor 工具提供了一种替代后端(该工具在默认情况下使用 Textifier 后端,生成如上所示类型的输出)。这个后端使 TraceClassVisitor 类的每个方 法都会打印用于调用它的 Java 代码。例如,调用 visitEnd() 方法将打印 cv.visitEnd();。其结果是,当一个具有 ASMifier 后端的 TraceClassVisitor 访问器访问一个类时,它会打印用 ASM 生成这个类的源代码。如果用这个访问器来访问一个已经存在的类,那这一点是很有用的。例如,如果你不知道如何用 ASM 生成某个已编译类,可以编写相应的源代码,用 javac 编译它,并用 ASMifier 来访问这个编译后的类。将会得到生成这个已编译类的 ASM 代码! ASMifier 类也可以在命令行中使用。例如,使用以下命令;

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

将会生成一些代码,经过缩进后,这些代码就是如下模样:

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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20