注解本质是继承java.lang.annotation.Annotation的接口,编译后为interface字节码,运行时通过动态代理(AnnotationInvocationHandler)实现,属性值从RuntimeVisibleAnnotations属性表读取。
Java 中的注解(比如 @Override、@Test 或自定义的 @MyLog)编译后**100% 是一个继承 java.lang.annotation.Annotation 的接口**,不是 class,也不是 abstract class。你可以用 IDEA 右键 → “Go to → Type Hierarchy”,会清楚看到它底下直接 extends Annotation;反编译 .class 文件也能确认:字节码里是 interface MyAnno extends Annotation,调用其方法用的是 INVOKEINTERFACE 指令——这是接口的铁证。
String value())都是接口里的抽象方法default "abc")由编译器在字节码中固化,运行时由代理对象“兜底返回”当你写 clazz.getDeclaredAnnotation(MyAnno.class),返回的不是真实类的实例,而是一个形如 $Proxy1 的代理对象,背后是 sun.reflect.annotation.AnnotationInvocationHandler ——它实现了 InvocationHandler,把所有方法调用(比如 anno.value())转成从字节码属性表中查常量值。
AnnotationInvocationH
andler 未实现 Serializable)== 或 equals() 比较两个同内容注解实例(它们是不同代理对象)只有 @Retention(RetentionPolicy.RUNTIME) 的注解,才会被 javac 写进 class 文件的 RuntimeVisibleAnnotations 属性表;CLASS 级别写进 RuntimeInvisibleAnnotations;SOURCE 级别编译完就丢弃,连 .class 都没留痕。
javap -v MyClass.class | grep -A 20 "RuntimeVisibleAnnotations" 直接看到注解原始数据(十六进制编码的属性名/值)getAnnotations())本质就是读这个属性表 + 调用 Proxy.newProxyInstance() 构建代理@Retention(RUNTIME),反射就永远拿不到;加了但没写对作用域(比如标在 private 字段上却用 getMethodAnnotations()),也会为空很多人写完注解一运行就报错,往往卡在底层类型限制上——这不是框架问题,而是 JVM 字节码规范死锁的规则。
属性类型只能是:基本类型、String、Class、枚举、其他注解,或它们的**一维数组**;List、Map、Object[]、int[][] 全都不合法default 值必须是编译期常量:不能是 new String("x")、不能是静态变量、不能是方法调用(哪怕该方法返回 const)value() 方法有特殊地位:当注解只含一个 value 属性时,使用可省略参数名(@MyAnno("ok"));但一旦加了第二个属性,就必须显式写全(@MyAnno(value="ok", level=2))@ 符号。