2.1接口和组件
2.2.1表现Presentation
生成和转换编译后的类的ASM API是基于ClassVisitor接口的(见图2.4)。在这个接口中的每一个方法都与类文件中有着相同名称的段相对应(见图2.1)。在访问类结构中简单的段时,是通过调用一个独立的方法来实现的,该方法的参数就是该段相关的内容,该方法的返回值为void。对长度任意并且较复杂的段进行访问时,是通过一个初始化方法返回一个辅助的visitor接口来实现,例如visitAnnotation,visitField以及visitMethod,它们都返回与之对应的接口AnnotationVisitor,FieldVisitor以及MethodVisitor。
这些规则也同样适用于这些辅助接口。例如,在FieldVisitor接口中的每个方法,都与类文件结构中与该名称(Field)对应的子结构对应(见图2.5),并且visitAnnotation并会一个辅助的AnnotationVisitor接口,与ClassVisitor中的AnnotationVisitor相同。关于这些辅助接口的创建和使用,将在下一章节介绍,这一章主要限于那些简单的问题,使用ClassVisitor接口就可以解决的。
图2.4 ClassVisitor接口
图2.5 FieldVisitor接口
对ClassVisitor接口中方法的调用必须遵循下面文档定义的顺序,该文档定义在ClassVisitor接口的Javadoc中。
visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )*( visitInnerClass | visitField | visitMethod )*
visitEnd
这就意味着visit必须被第一个调用,然后调用visitSource方法,最多调用一次,再接着是visitOuterClass,然后再调用任意次数的visitAnnotation或者visitAttribute方法,接着可以调用任意次数的visitInnerClass,visitiField或者visitMethod,顺序不限,在最后,调用visitEnd方法。
在ClassVisitor接口的基础上,ASM提供了三个组件来生成和转换类:
接下来,将结合具体的例子来展示如何使用这些组件来生成和转换类。
2.2.2解析类
解析一个已存在的类仅需要ClassReader这个组件。下面让我们以一个实例来展示如何解析类。假设,我们想要打印一个类的内容,我们可以使用javap这个工具。第一步,实现ClassVisitor这个接口,用来打印类的信息。下面是一个简单的实现:
public class ClassPrinter implements ClassVisitor {
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
System.out.println(name + " extends " + superName + " {");
}
public void visitSource(String source, String debug) {
}
public void visitOuterClass(String owner, String name, String desc) {
}
public AnnotationVisitor visitAnnotation(String desc,
boolean visible) {
return null;
}
public void visitAttribute(Attribute attr) {
}
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
}
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
System.out.println(" " + desc + " " + name);
return null;
}
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
System.out.println(" " + name + desc);
return null;
}
public void visitEnd() {
System.out.println("}");
}
}
第二步,将ClassPrinter和ClassReader结合起来,这样,ClassReader产生的事件就可以被我们的ClassPrinter消费了:
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
上面的第二行代码创建了一个ClassReader来解析Runnable类。最后一行代码中的accept方法解析Runnable类的字节码,并且调用cp上对应的方法。结果如下:
java/lang/Runnable extends java/lang/Object {
run()V
}
注意,这里有多种方式来构造一个ClassReader的实例。可以通过类名,例如上面的例子,或者通过类的字节数组。或者类的输入流。类的输入流可以通过ClassLoader的getResourceAdStream方法:
cl.getResourceAsStream(classname.replace(’.’, ’/’) + ".class");