# 4.2 注释
类、字段、方法和方法参数注释,比如 @Deprecated 或 @Override,只要它们的保留策略不是 RetentionPolicy.SOURCE,它们就会被存储在编译后的类中。这一信息不是在运行时供字节代码指令使用,但是,如果保留策略是 RetentionPolicy.RUNTIME,则可以通过反射 API 访问它。它还可以供编译器使用。
# 4.2.1 结构
源代码中的注释可以具有各种不同形式, 比如 @Deprecated 、 @Retention(RetentionPolicy.CLASS) 或 @Task(desc="refactor", id=1)。但在内部,所有注释的形式都是相同的,由一种注释类型和一组名称/值对规定,其中的取值仅限于如下几种:
- 基元,String 或 Class 值
- 枚举值
- 注释值
- 上述值的数组
注意,一个注释中可以包含其他注释,甚至可以包含注释数组。因此,注释可能非常复杂。
# 4.2.2 接口和组件
用于生成和转换注释的 ASM API 是基于 AnnotationVisitor 抽象类的(见图 4.3)。
图 4.3 AnnotationVisitor 类
public abstract class AnnotationVisitor {
public AnnotationVisitor(int api);
public AnnotationVisitor(int api, AnnotationVisitor av);
public void visit(String name, Object value);
public void visitEnum(String name, String desc, String value);
public AnnotationVisitor visitAnnotation(String name, String desc);
public AnnotationVisitor visitArray(String name);
public void visitEnd();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这个类的方法用于访问一个注释的名称/值对(注释类型在访问这一类型的方法中访问,即 visitAnnotation 方法)。第一个方法用于基元、String 和 Class 值(后者用 Type 对象表示),其他方法用于枚举、注释和数组值。可以按任意顺序调用它们,visitEnd 除外:
( visit | visitEnum | visitAnnotation | visitArray )* visitEnd
注意,两个方法返回 AnnotationVisitor:这是因为注释可以包含其他注释。另外,与 ClassVisitor 返回的 MethodVisitor 不同,这两个方法返回的 AnnotationVisitors 必须顺序使用:事实上,在完全访问一个嵌套注释之前,不能调用父访问器的任何方法。
还要注意,visitArray 方法返回一个 AnnotationVisitor,以访问数组的元素。但是, 由于数组的元素未被命名,因此,name 参数被 visitArray 返回的访问器的方法忽略,可以设定为 null。
- 添加、删除和检测注释
与字段和方法的情景一样,可以通过在 visitAnnotation 方法中返回 null 来删除注释:
public class RemoveAnnotationAdapter extends ClassVisitor {
private String annDesc;
public RemoveAnnotationAdapter(ClassVisitor cv, String annDesc) {
super(ASM4, cv);
this.annDesc = annDesc;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean vis) {
if (desc.equals(annDesc)) {
return null;
}
return cv.visitAnnotation(desc, vis);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
类注释的添加要更难一些,因为存在一些限制条件:必须调用 ClassVisitor 类的方法。事实上,所有可以跟在 visitAnnotation 之后的方法都必须重写,以检测什么时候已经访问了所有注释(因为 visitCode 方法的原因,方法注释的添加更容易一些):
public class AddAnnotationAdapter extends ClassVisitor {
private String annotationDesc;
private boolean isAnnotationPresent;
public AddAnnotationAdapter(ClassVisitor cv, String annotationDesc) {
super(ASM4, cv);
this.annotationDesc = annotationDesc;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
cv.visit(v, access, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (visible && desc.equals(annotationDesc)) {
isAnnotationPresent = true;
}
return cv.visitAnnotation(desc, visible);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
addAnnotation();
cv.visitInnerClass(name, outerName, innerName, access);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
addAnnotation();
return cv.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
addAnnotation();
return cv.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
addAnnotation();
cv.visitEnd();
}
private void addAnnotation() {
if (!isAnnotationPresent) {
AnnotationVisitor av = cv.visitAnnotation(annotationDesc, true);
if (av != null) {
av.visitEnd();
}
isAnnotationPresent = true;
}
}
}
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
注意,如果类版本低于 1.5,这个适配器将其更新至该版本。这是必要地,因为对于版本低于 1.5 的类,JVM 会忽略其中的注释。
注释在类和方法适配器中的最后一种应用情景,也可能是最常见的应用情景,就是以注释实现转换的参数化。例如,你可能仅对于那些具有@Persistent 注释的字段来转换字段的访问, 仅对于那些拥有@Log 注释的方法添加记录代码,如此等等。所有这些应用情景都可以很轻松地实现,因为注释是必须首先访问的:必须在字段和方法之前访问类注释,必须在代码之前访问方法和参数注释。因此,只需在检测到所需注释时设定一个标志,然后在后面的转换中使用,就像上面的例子用 isAnnotationPresent 标志所做的事情。
# 4.2.3 工具
2.3 节介绍的 TraceClassVisitor, CheckClassAdapter 和 ASMifier 类也支持注释 ( 就 像 对 于 方 法 一 样 , 还 可 能 使 用 TraceAnnotationVisitor 或 CheckAnnotationAdapter,在各个注释的级别工作,而不是在类级别工作)。它们可用于查看如何生成某个特定注释。例如,使用以下代码:
java -classpath asm.jar:asm-util.jar \1
org.objectweb.asm.util.ASMifier \
java.lang.Deprecated
2
3
将输出如下代码(经过微小的重构):
package asm.java.lang;
import org.objectweb.asm.*;
public class DeprecatedDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
AnnotationVisitor av;
cw.visit(V1_5, ACC_PUBLIC + ACC_ANNOTATION + ACC_ABSTRACT
+ ACC_INTERFACE, "java/lang/Deprecated", null, "java/lang/Object",
new String[]{"java/lang/annotation/Annotation"});
{
av = cw.visitAnnotation("Ljava/lang/annotation/Documented;", true);
av.visitEnd();
}
{
av = cw.visitAnnotation("Ljava/lang/annotation/Retention;", true);
av.visitEnum("value", "Ljava/lang/annotation/RetentionPolicy;",
"RUNTIME");
av.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
此代码说明如何用 ACC_ANNOTATION 标志创建一个注释类,并说明如何创建两个类注释, 一个没有值,一个具有枚举值。方法注释和参数注释可以采用 MethodVisitor 类中定义的 visitAnnotation 和 visitParameterAnnotation 方法以类似方式创建。