2.3工具
除了ClassVisitor接口,以及与之相关的三个组件ClassReader ClassAdapter和ClassWriter,ASM在org.objectweb.asm.util包中提供了一些工具用来帮助开发类生成器或者适配器,这些工具在运行时并不需要。ASM提供了一些实用类用来在运行时操作内部名称,类型描述符以及方法描述符。所有的这些工具将在下面介绍。 2.3.1类型 就像在前面章节中见到的,ASM API将编译后的class中的java类型以内部名称或者类型描述符的方式展现出来。当然,也可以将这些类型还原成源代码中定义的样子,这样就更便于阅读。但是,这需要在ClassReader和ClassWriter之间进行系统转换,但是这会降低性能。这就是为什么ASM没有透明地转换内部名称和类型描述符为源代码中对等的形式。尽管如此,ASM还是提供了Type类用来在需要的时候手工地进行转换。 一个Type对象代表了一个java类型,它可以通过类型描述符或者Class对象来构造。这个Type类也包含了一些静态变量,用来表示基本类型,例如Type.INT_TYPE是int类型的Type对象。 getInternalName方法返回一个Type的内部名称,例如,Type.getType(String.class).getInternalName()返回了String类的内部名称”java/lang/String”.这个方法只能用于类或者借口类型。 getDescriptor方法返回一个Type的描述符,例如,你可以使用Type.getType(String.class).getDescriptor()来代替”Ljava/lang/String;”。或者,使用Type.INT_TYPE.getDescriptor()来代替I。 Type类也提供了一些静态方法用来获取一个方法的参数的Type对象和返回值的Type对象,主要是通过它的类型描述符或者java.lang.reflect.Method对象来获得。例如,Type.getArgumentTypes(“(I)V”)返回一个包含Type.INT_TYPE的数组,同样地Type.getReturnType(“(I)V”)返回一个Type.VOID_TYPE对象。 2.3.2 TraceClassVisitor 为了检查类的生成或者转换是否如你期望,单靠ClassWriter返回的字节数组是没有多大帮助的,因为它不可读。相比较而言,一个文本表示更易于阅读和使用,而这就是TraceClassVisitor提供的。这个类,就如它的名字暗示的一样,实现了ClassVisitor借口,并且构造解析过的类的文本表示。因此,你可以使用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之前使用。注意,通过这个类生成的类的文本表示可以通过String.equals()很容易地进行比较。 2.3.3 CheckClassAdapter ClassWriter并不会检查它的方法调用是否按照合适的顺序以及参数是否有效。这样就可能生成无效的代码,而被java虚拟机的验证工具所拒绝。为了尽可能地检测出这些错误,可以使用CheckClassAdapter。和TraceClassVisitor一样,这个类也实现了ClassVisitor接口,它也会将对它的方法调用委托给其他的ClassVisitor,例如一个TraceClassVisitor或者ClassWriter。尽管如此,除了打印类的文本表示,这个类会在将方法调用委托给下一个ClassVisitor之前,检查对它的方法调用顺序是否合理,以及参数是否有效。如果发生错误,将会抛出IllegalStateException或者IllegalArgumentException。 为了检查一个类,并且打印它的文本表示,最终创建一个字节数组,你可以参考下面的代码: ClassWriter cw = new ClassWriter(0); TraceClassVisitor tcv = new TraceClassVisitor(cw, printWriter); CheckClassAdapter cv = new CheckClassAdapter(tcv); cv.visit(...); ... cv.visitEnd(); byte b[] = cw.toByteArray(); 注意,如果这些ClassVisitor的顺序不同,那么它们会以不同的顺序执行。例如,下面的代码会导致在跟踪代码以后再检查类。 ClassWriter cw = new ClassWriter(0); CheckClassAdapter cca = new CheckClassAdapter(cw); TraceClassVisitor cv = new TraceClassVisitor(cca, printWriter); 就像TraceClassVisitor一样,为了检查类是否有效,你也可以在生成类或者转换类的链的任 何节点使用CheckClassAdapter,而不仅仅是在ClassWriter之前。 2.3.4 ASMifierClassVisitor 这个类也实现了ClassVisitor接口,它的每个方法会打印出调用它的java代码。例如,调用visitEnd会打印出cv.visitEnd();,结果是,当这个visitor解析一个类时,它会打印出使用ASM来生成这个类的源代码。当你使用这个类去解析一个存在的类时,你会发现它很有用。例如,如果你不知道如何使用ASM来生成一些编译后的类,那么你可以先写出这些类的源代码,然后使用javac来编译,再然后使用ASMifierClassVisitor来解析,这样就能够得到使用ASM来生成这些类的源代码了。 ASMifierClassVisitor可以通过命令行直接使用,如下面的例子: java -classpath asm.jar:asm-util.jar \ org.objectweb.asm.util.ASMifierClassVisitor \ 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();