组件组成
到目前为止,我们只看到了如何创建和转换ClassNode对象,但是还没有看到如何从类的字节数组表示形式构造ClassNode,反之亦然,如何从ClassNode构造此字节数组。
实际上,这是通过组合核心API和树API组件来完成的,如本节所述。
介绍
除了图6.1中所示的字段之外,ClassNode类还扩展了ClassVisitor类,并且还提供了一个接受方法,该方法将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, ...);
...
}
}
构造方式
因此,可以通过将字节数组与ClassReader组合在一起来构造ClassNode,以使ClassReader组件消耗ClassReader生成的事件,从而对其字段进行初始化(从上面的代码可以看出) :
ClassNode cn = new ClassNode();
ClassReader cr = new ClassReader(...);
cr.accept(cn, 0);
通过将ClassNode与ClassWriter进行组合,可以对称地将ClassNode转换为其字节数组表示形式,这样ClassWriter就可以使用ClassNode的accept方法生成的事件:
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
byte[] b = cw.toByteArray();
模式
通过将以下元素放在一起,可以使用tree API转换类:
ClassNode cn = new ClassNode(ASM4);
ClassReader cr = new ClassReader(...);
cr.accept(cn, 0);
... // here transform cn as you want
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;
// put your transformation code here
cn.accept(next);
}
}
此模式使用两个对象代替一个对象,但是与第一个模式完全相同:
接收的事件用于构造ClassNode,当接收到最后一个事件时,将其转换并转换回基于事件的表示形式。
两种模式都允许您将基于树的类适配器与基于事件的适配器组成。
它们也可以用于组合基于树的适配器,但是,如果您仅需要组合基于树的适配器,则这不是最佳解决方案:
在这种情况下,使用 ClassTransformer 之类的类将避免在两种表示形式之间进行不必要的转换。