注解
如果类,字段,方法和方法参数注解(例如@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
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除外:
( visit | visitEnum | visitAnnotation | visitArray )* visitEnd
请注意,有两个方法返回AnnotationVisitor:这是因为注解可以包含其他注解。
也不同于ClassVisitor返回的MethodVisitors,这两个方法返回的AnnotationVisitors必须顺序使用:
实际上,在完全访问嵌套注解之前,不必调用父访问者的方法。
还要注意,visitArray方法将AnnotationVisitor返回到访问数组的元素。
但是,由于未命名数组的元素,所以visitArray返回的访问者的方法将忽略name参数,并且可以将其设置为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);
}
}
增加
由于必须调用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) {
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在单个注解级别而不是在类级别使用)。
它们可以用来查看如何生成一些特定的注解。
例如使用:
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Deprecated
打印经过少量重构后的代码,其内容为:
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方法。