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

这个类的方法用于访问一个注释的名称/值对(注释类型在访问这一类型的方法中访问,即 visitAnnotation 方法)。第一个方法用于基元、String 和 Class 值(后者用 Type 对象表示),其他方法用于枚举、注释和数组值。可以按任意顺序调用它们,visitEnd 除外:

( visit | visitEnum | visitAnnotation | visitArray )* visitEnd
1

注意,两个方法返回 AnnotationVisitor:这是因为注释可以包含其他注释。另外,与 ClassVisitor 返回的 MethodVisitor 不同,这两个方法返回的 AnnotationVisitors 必须顺序使用:事实上,在完全访问一个嵌套注释之前,不能调用父访问器的任何方法。

还要注意,visitArray 方法返回一个 AnnotationVisitor,以访问数组的元素。但是, 由于数组的元素未被命名,因此,name 参数被 visitArray 返回的访问器的方法忽略,可以设定为 null。

  1. 添加、删除和检测注释

与字段和方法的情景一样,可以通过在 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);
    }
}
1
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;
        }
    }
}
1
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
1
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();
    }
}
1
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 类中定义的 visitAnnotationvisitParameterAnnotation 方法以类似方式创建。