到现在为止,我们只是看到了如何创建和转换 ClassNode 对象,但还没有看到如何由一个类的字节数组表示来构造一个 ClassNode,或者反过来,由 ClassNode 构造这个字节数组。事实上,这一功能可以通过合成核心 API 和树 API 组件来完成,本节就来解释这一内容。
除了图 6.1 所示的字段之外,ClassNode 类扩展了 ClassVisitor 类,还提供了一个 accept 方法,它以一个 ClassVisitor 为参数。Accept 方法基于 ClassNode 字段值生成事件,而 ClassVisitor 方法执行逆操作,即根据接到的事件设定 ClassNode 字段:
public class ClassNode extends ClassVisitor {
...
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces[]) {
this.version = version;
this.access = access;
this.name = name;
this.signature = signature;
...
}
...
public void accept(ClassVisitor cv) {
cv.visit(version, access, name, signature, ...);
...
}
}
要由字节数组构建 ClassNode,可以将它与 ClassReader 合在一起,使 ClassReader 生成的事件可供 ClassNode 组件使用,从而初始化其字段(由上述代码可以看出):
ClassNode cn = new ClassNode();
ClassReader cr = new ClassReader(...);
cr.accept(cn, 0);
反过来,可以将 ClassNode 转换为其字节数组表示,只需将它与 ClassWriter 合在一起即可,从而使 ClassNode 的 accept 方法生成的事件可供 ClassWriter 使用:
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
byte[] b = cw.toByteArray();
要用树 API 转换类,可以将这些元素放在一起:
ClassNode cn = new ClassNode(ASM4);
ClassReader cr = new ClassReader(...);
cr.accept(cn, 0);
... // 可以在这里根据需要转换 cn
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
byte[] b = cw.toByteArray();
还可能与核心 API 一起使用基于树的类转换器,比如类适配器。有两种常见模式可用于此种情景。第一种模式使用继承:
public class MyClassAdapter extends ClassNode {
public MyClassAdapter(ClassVisitor cv) {
super(ASM4);
this.cv = cv;
}
@Override
public void visitEnd() {
// put your transformation code here
accept(cv);
}
}
当这个类适配器用在一个经典的转换链时:
ClassWriter cw = new ClassWriter(0);
ClassVisitor ca = new MyClassAdapter(cw);
ClassReader cr = new ClassReader(...);
cr.accept(ca, 0);
byte[] b = cw.toByteArray();
cr 生成的事件供 ClassNode ca 使用,从而初始化这个对象的字段。最后,在使用 visitEnd 事件时,ca 执行转换,并通过调用其 accept 方法,生成与所转换类对应的新事件,然后由 cw 使用。如果假定 ca 改变了类版本,则相应原程序图如图 6.2 所示。
与图 2.7 中 ChangeVersionAdapter 的程序图进行对比,可以看出,ca 和 cw 之间的事件发生在 cr 和 ca 之间的事件之后,而不是像正常类适配器一样同时进行。事实上,对于所有基于树的转换都是如此,同时还解释了为什么它们受到的限制要少于基于事件的转换。
第二种模式可用于以类似程序图获得相同结果,它使用的是委托而非继承:
public class MyClassAdapter extends ClassVisitor {
ClassVisitor next;
public MyClassAdapter(ClassVisitor cv) {
super(ASM4, new ClassNode());
next = cv;
}
@Override
public void visitEnd() {
ClassNode cn = (ClassNode) cv;
// 将转换代码放在这里
cn.accept(next);
}
}
这一模式使用两个对象而不是一个,但其工作方式完全与第一种模式相同:接收到的事件用于构造一个 ClassNode,它被转换,并在接收到最后一个事件后,变回一个基于事件的表示。
这两种模式都允许用基于事件的适配器来编写基于树的类适配器。它们也可用于将基于树的适配器组合在一起,但如果只需要组合基于树的适配器,那这并非最佳解决方案:在这种情况下, 使用诸如 ClassTransformer 的类将会避免在两种表示之间进行不必要的转换。