注解本质是继承java.lang.annotation.Annotation的接口,编译后为interface字节码,运行时通过动态代理(AnnotationInvocationHandler)实现,属性值从RuntimeVisibleAnnotations属性表读取。

注解本质就是 interface,不是类也不是抽象类
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")由编译器在字节码中固化,运行时由代理对象“兜底返回”
运行时拿到的注解对象其实是 JDK 动态代理实例
当你写 clazz.getDeclaredAnnotation(MyAnno.class),返回的不是真实类的实例,而是一个形如 $Proxy1 的代理对象,背后是 sun.reflect.annotation.AnnotationInvocationHandler ——它实现了 InvocationHandler,把所有方法调用(比如 anno.value())转成从字节码属性表中查常量值。
- 这意味着:注解对象不可序列化(
AnnotationInvocationHandler未实现Serializable) - 也意味着:你不能用
==或equals()比较两个同内容注解实例(它们是不同代理对象) - 如果你在单元测试里 mock 注解行为,得 mock 接口本身,而不是“构造一个注解类”——它根本没法 new
注解信息存在哪?全靠字节码里的 RuntimeVisibleAnnotations
只有 @Retention(RetentionPolicy.RUNTIME) 的注解,才会被 javac 写进 class 文件的 RuntimeVisibleAnnotations 属性表;CLASS 级别写进 RuntimeInvisibleAnnotations;SOURCE 级别编译完就丢弃,连 .class 都没留痕。
家电公司网站源码是一个以米拓为核心进行开发的家电商城网站模板,程序采用metinfo5.3.9 UTF8进行编码,软件包含完整栏目与数据。安装方法:解压上传到空间,访问域名进行安装,安装好后,到后台-安全与效率-数据备份还原,恢复好数据后到设置-基本信息和外观-电脑把网站名称什么的改为自己的即可。默认后台账号:admin 密码:132456注意:如本地测试中127.0.0.1无法正常使用,请换成l
- 你可以用
javap -v MyClass.class | grep -A 20 "RuntimeVisibleAnnotations"直接看到注解原始数据(十六进制编码的属性名/值) - 反射 API(如
getAnnotations())本质就是读这个属性表 + 调用Proxy.newProxyInstance()构建代理 - 所以:没有
@Retention(RUNTIME),反射就永远拿不到;加了但没写对作用域(比如标在 private 字段上却用getMethodAnnotations()),也会为空
自定义注解时最容易忽略的三个硬约束
很多人写完注解一运行就报错,往往卡在底层类型限制上——这不是框架问题,而是 JVM 字节码规范死锁的规则。
立即学习“Java免费学习笔记(深入)”;
-
属性类型只能是:基本类型、String、Class、枚举、其他注解,或它们的**一维数组**;List、Map、Object[]、int[][]全都不合法 -
default 值必须是编译期常量:不能是new String("x")、不能是静态变量、不能是方法调用(哪怕该方法返回 const) -
value() 方法有特殊地位:当注解只含一个value属性时,使用可省略参数名(@MyAnno("ok"));但一旦加了第二个属性,就必须显式写全(@MyAnno(value="ok", level=2))
@ 符号。









