# 10.2 规则
本节给出一些规则,在使用 ASM 树 API 时,要想确保你的代码在所有未来 ASM 版本中都保持有效(其意义见 5.1.1 节定义的约定),就必须遵循这些规则。
首先,如果使用树 API 编写一个类生成器,那就不需要遵循什么规则(和核心 API 一样)。可以用任意构造器创建 ClassNode 和其他元素,可以使用这些类的任意方法。
另一方面,如果要用树 API 编写类分析器或类适配器,也就是说,如果使用 ClassNode 或其他直接或间接地通过 ClassReader.accept()填充的类似类,或者如果重写这些类中的一个,则必须遵循下面给出的规则。
# 10.2.1 基本规则
- 创建类节点
考虑这样一种情景,我们创建一个 ClassNode
,通过一个 ClassReader
填充它,然后分析或转换它,最终根据需要用 ClassWriter
写出结果(这一讨论及相关规则同样适用于其他节点类;对于由别人创建的 ClassNode,其分析或转换在下一节讨论)。在这种情况下,仅有一条规则:
规则 3:要用 ASM 版本X 的树 API 编写类分析器或适配器,则使用以这一确切版本为参数的构造器创建 ClassNode(而不是使用没有参数的默认构造器)。
本规则的目的是在通过一个 ClassReader 填充 ClassNode 时,如果遇到未知特性,则抛出一个错误(根据后向兼容性约定的定义)。如果不遵循这一规则,在以后遇到未知元素时,你 的分析或转换代码可能会失败,也许能够成功运行,但却因为没有忽略这些未知元素而给出错误结果。换言之,如果不遵循这一规则,可能无法保证约定的最后一项条款。
如何做到呢?ASM 4.0 内部对 ClassNode 的实现如下(这里重复使用 5.1.2 节的示例):
public class ClassNode extends ClassVisitor {
public ClassNode() {
super(ASM4, null);
}
public ClassNode(int api) {
super(api, null);
}
...
public void visitSource(String source, String debug) {
// 将 source 和 debug 存储在局部字段中...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
在 ASM 5.0 中,这一代码变为:
public class ClassNode extends ClassVisitor {
...
public void visitSource(String source, String debug) {
if (api < ASM5) {
// 将source 和 debug 存储在局部字段中...
} else {
visitSource(null, source, debug);
}
}
public void visitSource(Sring author, String source, String debug) {
if (api < ASM5) {
if (author == null) visitSource(source, debug);
else
throw new RuntimeException();
} else {
// 将author、source 和 debug 存储在局部字段中...
}
}
public void visitLicense(String license) {
if (api < ASM5) throw new RuntimeException();
// 将 license 存储在局部字段中
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
如果使用 ASM 4.0,那创建 ClassNode(ASM4)没有什么特别之处。但如果升级到 ASM 5.0,但不修改代码,那就会得到一个 ClassNode 5.0,它的 api 字段将为 ASM4 < ASM5。于是容易看出,如果输入类包含一个非 null 作者或许可属性,那通过 ClassReader 填充ClassNode 时将会失败,如约定中的定义。如果还升级你的代码,将 api 字段改为 ASM5,并升级剩余代码,将这些新属性考虑在内,那在填充代码时就不会抛出错误。
注意,ClassNode 5.0 代码非常类似于 ClassVisitor 5.0 代码。这是为了确保在定义 ClassNode 的子类时能够拥有正确的语义(类似于 ClassVisitor 的子类——见 10.2.2 节)。
- 使用现有类代码
如果你的类分析器或适配器收到别人创建的 ClassNode,那你就不能肯定在创建它时传送 给其构造器的 ASM 版本。当然可以自行检查 api 字段,但如果发现这个版本高于你支持的版本, 直接拒绝这个类可能太过保守了。事实上,这个类中可能没有包含任何未知特性。另一方面,你不能检查是否存在未知特性(在我们的示例情景中,在为 ASM 4.0 编写代码时,你如何判断你的 ClassNode 中不存在未知的 license 字段呢?因为你在这里还不知道未来会添加这样一个字段)。于是设计了 ClassNode.check()方法来解决这个问题。这就引出了以下规则:
规则 4:要用 ASM 版本 X 的树 API 编写一个类分析器或适配器,使用别人创建的ClassNode,在以任何方式使用这个 ClassNode 之前,都要以这个确切版本号为参数,调用它的 check()方法。
其目的与规则 3 相同:如果不遵循这一规则,可能无法保证约定的最后一项条款。如何做到的呢?这个检查方法在 ASM 4.0 内部的实现如下:
public class ClassNode extends ClassVisitor {
...
public void check(int api) {
// 不做任何事
}
}
2
3
4
5
6
7
在 ASM 5.0中,这一代码变为:
public class ClassNode extends ClassVisitor {
...
public void check(int api) {
if (api < ASM5 && (author != null || license != null)) {
throw new RuntimeException();
}
}
}
2
3
4
5
6
7
8
9
如果你的代码是为 ASM 4.0 编写的,而且如果得到一个 ClassNode 4.0,它的 api 字段将为 ASM4,这样不会有问题,check 也不做任何事情。但如果你得到一个 ClassNode 5.0,如果这个节点实际上包含了非 null author 或 license,也就是说,它包含了 ASM 4.0 中未知的新特性,那 check(ASM4)方法将会失败。
注意:如果你自己创建 ClassNode,也可以使用这一规则。那就不需要遵循规则 3,也就是说,不需要在ClassNode 构造器中指明 ASM 版本。这一检查将在 check 方法中进行(但在填充 ClassNode 时,这种做法的效率要低于在之前进行检查)。
# 10.2.2 继承规则
如果希望提供 ClassNode 的子类或者其他类似节点类,那么规则 1 和 2 都是适用的。注意, 在一个 MethodNode 匿名子类的一个常用特例中,visitEnd()方法被重写:
class MyClassVisitor extends ClassVisitor {
...
public MethodVisitor visitMethod(...) {
final MethodVisitor mv = super.visitMethod(...);
if (mv != null) {
return new MethodNode(ASM4) {
public void visitEnd() {
// perform a transformation accept(mv);
}
};
}
return mv;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
那就自动适用规则 2(匿名类不能被重写,尽管没有明确将它声明为 final 的)。你只需要遵循规则 3,也就是说,在 MehtodNode 构造器中指定 ASM 版本(或者遵循规则 4,也就是在执行转换之前调用 check(ASM4))。
# 10.2.3 其他包
asm.util 和 asm.commons 中的类都有两个构造函数变体:一个有 ASM 版本参数,一个没有。
如果只是希望像 asm.util 中的 ASMifier、Textifier 或 CheckXxx Adapter 类或者asm.commons 包中的任意类一样,加以实例化和应用,那可以用没有 ASM 版本参数的构造器来实例化它们。也可以使用带有 ASM 版本参数的构造器,那就会不必要地将这些组件限制于特定的 ASM 版本(而使用无参数构造器相当于在说“使用最新的 ASM 版本”)。这就是为什么使用 ASM 版本参数的构造器被声明为 protected。
另一方面,如果希望重写 asm.util 中的 ASMifier、Textifier 或 CheckXxx Adapter 类或者 asm.commons 包中的任意类,那适用规则 1 和 2。具体来说,你的构造器必须以你希望用作参数的 ASM 版本来调用 super(…)。
最后,如果希望使用或重写 asm.tree.analysis 中的 Interpreter 类或其子类,必须做出同样的区分。还要注意,在使用这个分析包之前,创建一个 MethodNode 或者从别人那里获取一个,那在将这一代码传送给 Analyzer 之前必须使用规则 3 和 4。
← 10.1 介绍 A.1 字节代码指令 →