# 6.2 组件合成

到现在为止,我们只是看到了如何创建和转换 ClassNode 对象,但还没有看到如何由一个类的字节数组表示来构造一个 ClassNode,或者反过来,由 ClassNode 构造这个字节数组。事实上,这一功能可以通过合成核心 API 和树 API 组件来完成,本节就来解释这一内容。

# 6.2.1 介绍

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

要由字节数组构建 ClassNode,可以将它与 ClassReader 合在一起,使 ClassReader 生成的事件可供 ClassNode 组件使用,从而初始化其字段(由上述代码可以看出):

ClassNode cn = new ClassNode(); 
ClassReader cr = new ClassReader(...); 
cr.accept(cn, 0);
1
2
3

反过来,可以将 ClassNode 转换为其字节数组表示,只需将它与 ClassWriter 合在一起即可,从而使 ClassNode 的 accept 方法生成的事件可供 ClassWriter 使用:

ClassWriter cw = new ClassWriter(0); 
cn.accept(cw);
byte[] b = cw.toByteArray();
1
2
3

# 6.2.2 模式

要用树 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();
1
2
3
4
5
6
7

还可能与核心 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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

当这个类适配器用在一个经典的转换链时:

ClassWriter cw = new ClassWriter(0); 
ClassVisitor ca = new MyClassAdapter(cw); 
ClassReader cr = new ClassReader(...); 
cr.accept(ca, 0);
byte[] b = cw.toByteArray();
1
2
3
4
5

cr 生成的事件供 ClassNode ca 使用,从而初始化这个对象的字段。最后,在使用 visitEnd 事件时,ca 执行转换,并通过调用其 accept 方法,生成与所转换类对应的新事件,然后由 cw 使用。如果假定 ca 改变了类版本,则相应原程序图如图 6.2 所示。

图 6.2 MyClassAdapter 的程序图

与图 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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这一模式使用两个对象而不是一个,但其工作方式完全与第一种模式相同:接收到的事件用于构造一个 ClassNode,它被转换,并在接收到最后一个事件后,变回一个基于事件的表示。

这两种模式都允许用基于事件的适配器来编写基于树的类适配器。它们也可用于将基于树的适配器组合在一起,但如果只需要组合基于树的适配器,那这并非最佳解决方案:在这种情况下, 使用诸如 ClassTransformer 的类将会避免在两种表示之间进行不必要的转换。