注解

如果类,字段,方法和方法参数注解(例如@Deprecated或@Override)存储在已编译的类中,则它们的保留策略不是RetentionPolicy.SOURCE。

该信息在运行时不会由字节码指令使用,但是如果保留策略为RetentionPolicy.RUNTIME,则可以通过反射API进行访问。

编译器也可以使用它。

结构

结构体

源代码中的注解可以采用多种形式,例如@ Deprecated,@ Retention(RetentionPolicy.CLASS)或@Task(desc =“ refactor”,id = 1)。

但是,在内部,所有注解都具有相同的形式,并由注解类型和一组名称/值对指定,其中值限于:

  • 基本,字符串或类值,

  • 枚举值,

  • 注解值,

  • 以上值的数组。

请注意,注解可以包含其他注解,甚至可以包含注解数组。

因此,注解可能非常复杂。

Interfaces and components

用于生成和转换注解的ASM API基于AnnotationVisitor抽象类(请参见图4.3)。

  • Figure 4.3.: The AnnotationVisitor class
  [java]
1
2
3
4
5
6
7
8
9
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(); }

此类的方法用于访问注解的名称值对(在返回此类型的方法(即visitAnnotation方法)中访问注解类型)。

第一种方法用于原始值,String和Class值(后一种由Type对象表示),其他方法用于枚举,注解和数组值。

可以按任何顺序调用它们,但visitEnd除外:

  [plaintext]
1
( visit | visitEnum | visitAnnotation | visitArray )* visitEnd

请注意,有两个方法返回AnnotationVisitor:这是因为注解可以包含其他注解。

也不同于ClassVisitor返回的MethodVisitors,这两个方法返回的AnnotationVisitors必须顺序使用:

实际上,在完全访问嵌套注解之前,不必调用父访问者的方法。

还要注意,visitArray方法将AnnotationVisitor返回到访问数组的元素。

但是,由于未命名数组的元素,所以visitArray返回的访问者的方法将忽略name参数,并且可以将其设置为null。

添加,删除和检测注解

删除

像字段和方法一样,可以通过在visitAnnotation方法中返回null来删除注解:

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

增加

由于必须调用ClassVisitor类的方法的限制,添加类注解会更加困难。

实际上,必须重写visitAnnotation之后的所有方法,以检测何时访问了所有注解(由于使用了visitCode方法,方法注解更易于添加):

  [java]
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
58
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) { int v = (version & 0xFF) < V1_5 ? V1_5 : version; 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.5。

这是必需的,因为JVM会忽略版本小于1.5的类中的注解。

在类和方法适配器中,注解的最后一个(也是最常见的)用例是使用注解以参数化转换。

例如,您可以仅对以下字段进行字段访问转换:

具有 @Persistent 批注,仅将日志记录代码添加到具有 @Log 批注的方法中,依此类推。

所有这些用例都可以轻松实现,因为必须首先访问注解:必须在字段和方法之前访问类注解,并且必须在代码之前访问方法和参数注解。

因此,只要在检测到所需注解时设置一个标志,然后在转换中稍后使用它就足够了,就像上述示例中使用isAnnotationPresent标志所做的那样。

工具类

TraceClassVisitor,CheckClassAdapter和ASMifier类, 在第2.3节中发送的消息也支持注解(与方法一样,也可以使用TraceAnnotationVisitor或CheckAnnotationAdapter在单个注解级别而不是在类级别使用)。

它们可以用来查看如何生成一些特定的注解。

例如使用:

  [plaintext]
1
2
3
java -classpath asm.jar:asm-util.jar \ org.objectweb.asm.util.ASMifier \ java.lang.Deprecated

打印经过少量重构后的代码,其内容为:

  [java]
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
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(); } }

这段代码显示了两个如何使用ACC_ANNOTATION标志创建注解类,并显示了如何创建两个类注解,一个没有值,一个带有枚举值。 可以用类似的方法来创建方法和参数注解,方法是在MethodVisitor类中定义了visitAnnotation和visitParameterAnnotation方法。

参考文档

https://asm.ow2.io/asm4-guide.pdf