反射无法读取source注解,因其在编译期即被javac彻底擦除,字节码中根本不存在,jvm加载时无元数据可构建,故getannotation()等api必返回null。

反射无法读取 SOURCE 阶段注解,不是因为反射“能力不足”,而是因为这类注解在字节码中根本不存在——它早在编译完成前就被 javac 彻底擦除了。
SOURCE 注解在编译期就被丢弃,反射无从下手
Java 编译器(javac)对 @Retention(RetentionPolicy.SOURCE) 的处理是硬性规则:仅用于源码分析,不写入 .class 文件的任何结构。反编译后完全看不到该注解的痕迹,JVM 加载类时自然无法构建其元数据,反射 API(如 getAnnotation())也就没有数据可读。
- 编译后字节码中不含注解属性表(RuntimeVisibleAnnotations / RuntimeInvisibleAnnotations)
- Class 对象的内部元数据(如
annotationData)中不会初始化对应条目 - 调用
isAnnotationPresent()或getAnnotation()会直接返回false或null,不抛异常,也不触发任何加载逻辑
与 CLASS 策略的本质区别:存在 ≠ 可见
CLASS 级注解虽写入了 .class 文件,但 JVM 类加载器默认不将其加载进运行时元空间(Metaspace),因此反射仍不可见。而 SOURCE 连写入这一步都没发生,属于“物理消失”。这是两个不同层级的限制:
- SOURCE:编译器主动剔除 → 字节码里没有 → 反射无源可查
- CLASS:字节码里有,但类加载器跳过解析 → 元空间里没有 → 反射无据可依
- RUNTIME:字节码中有 + JVM 加载时解析并缓存 → 元空间中可定位 → 反射可访问
为什么设计 SOURCE 策略?性能与职责分离
保留策略不是技术缺陷,而是有意为之的设计取舍。SOURCE 注解服务于编译期工具链,它的“消失”恰恰保障了运行时零开销:
- 避免反射扫描、代理生成、元数据缓存等运行时成本
- 防止 IDE 提示、Lombok 生成、CheckStyle 规则等开发期能力污染生产环境
- 明确划分阶段边界:源码检查归编译器,动态行为归运行时框架
误用 SOURCE 导致的典型现象
开发者常因未注意保留策略,把本该用于运行时控制的注解声明为 SOURCE,结果出现“明明写了却读不到”的困惑:
- 自定义日志注解标注了
@Retention(SOURCE),AOP 切面始终不生效 - 权限校验注解被设为 SOURCE,Spring Security 无法识别方法级权限标记
- 单元测试中用
@Test模拟注解,但没配 RUNTIME,导致测试发现器跳过该方法
不复杂但容易忽略:只要注解要参与运行时逻辑,@Retention(RetentionPolicy.RUNTIME) 就不是可选项,而是必要前提。










