ASM通过重写ClassVisitor的visitAnnotation和visitTypeAnnotation方法捕获声明式注解与类型注解,核心是获取注解描述符、可见性及AnnotationVisitor回调链。

ASM 通过 ClassVisitor 读取类的注解字节码,核心在于重写其 visitAnnotation 和 visitTypeAnnotation 方法,分别处理**声明式注解**(如类、字段、方法上的 @Deprecated)和**类型注解**(如 @NonNull String 中的 @NonNull)。关键不是“解析注解值”,而是捕获注解的**描述符(descriptor)**、**可见性(visible/invisible)** 及其内部的 AnnotationVisitor 回调链。
1. 注解在字节码中的两种存在形式
Java 字节码中注解分为两类,对应不同访问入口:
-
RuntimeVisibleAnnotations / RuntimeInvisibleAnnotations:JVM 运行时可通过反射获取的注解(
RetentionPolicy.RUNTIME),对应ClassVisitor.visitAnnotation(String descriptor, boolean visible) -
RuntimeVisibleTypeAnnotations / RuntimeInvisibleTypeAnnotations:作用于类型使用位置(如泛型、throws、new 表达式等)的注解,对应
ClassVisitor.visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible)
2. 在 ClassVisitor 中捕获类级别注解
重写 visitAnnotation 即可拿到类声明上的注解信息。注意:此处的 descriptor 是注解类型的内部名称(如 "Ljava/lang/Deprecated;"),不是简单类名。
public class AnnotationCollectingVisitor extends ClassVisitor {
private final List<String> classAnnotations = new ArrayList<>();
<pre class="brush:php;toolbar:false;">public AnnotationCollectingVisitor() {
super(Opcodes.ASM9);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
// 记录注解类型描述符(如 @Test → "Lorg/junit/Test;")
classAnnotations.add(descriptor);
// 返回一个 AnnotationVisitor 实例,用于进一步读取注解成员值(如 @RequestMapping(value="/api", method=GET))
return new AnnotationVisitor(Opcodes.ASM9) {
@Override
public void visit(String name, Object value) {
System.out.println(" → " + name + " = " + value);
}
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
System.out.println(" → nested annotation " + name + ": " + descriptor);
return this;
}
@Override
public AnnotationVisitor visitArray(String name) {
System.out.println(" → array " + name);
return new AnnotationVisitor(Opcodes.ASM9) {
@Override
public void visit(String name, Object value) {
System.out.println(" element: " + value);
}
};
}
};
}
public List<String> getClassAnnotations() {
return classAnnotations;
}}
立即学习“Java免费学习笔记(深入)”;
3. 使用方式:解析 .class 文件或字节数组
用 ClassReader 加载字节码,传入自定义 ClassVisitor:
// 方式一:从 .class 文件读取
ClassReader reader = new ClassReader(Files.readAllBytes(Paths.get("MyClass.class")));
<p>// 方式二:从已加载类获取字节码(需 ClassWriter 或 Instrumentation)
byte[] bytes = ...; // 例如通过 ClassLoader.getResourceAsStream + readFully
ClassReader reader = new ClassReader(bytes);</p><p>AnnotationCollectingVisitor visitor = new AnnotationCollectingVisitor();
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG); // SKIP_CODE 可提速,跳过方法体</p><p>System.out.println("Class annotations: " + visitor.getClassAnnotations());注意:ClassReader.SKIP_CODE 会跳过方法字节码,但**不影响注解读取**;若还需分析方法/字段上的注解,需去掉该标志,并在对应 MethodVisitor/FieldVisitor 中重写 visitAnnotation。
4. 类型注解(Type Annotations)需额外处理
若目标是像 List 或 void m() throws @Critical Exception 这类注解,必须重写 visitTypeAnnotation 并解析 TypePath 判断注解作用位置:
-
typeRef编码了注解所在的上下文(如TypeReference.METHOD_TYPE_PARAMETER、TypeReference.CLASS_EXTENDS) -
typePath描述嵌套层级(如泛型第 0 个参数的第 1 层类型),可用TypePath.toString()辅助调试 - 同样需返回
AnnotationVisitor来读取具体键值对


















