Java注解本质是interface,需配合@Retention(RUNTIME)、@Target等元注解及正确ClassLoader才能通过反射获取;否则返回null且无警告。

注解本质是接口,不是“配置”或“标签”
Java 注解是编译期的元数据契约,底层是 interface,由 JVM 和工具(如编译器、反射 API)按需读取。它不执行逻辑,也不改变字节码行为——除非你写了配套的处理器(比如 AnnotationProcessor 或运行时反射调用)。很多人误以为加个 @Override 就“启用”了重写检查,其实真正干活的是 javac;加个 @Test 也不自动跑测试,得靠 JUnit 的反射扫描和调用。
常见错误现象:java.lang.annotation.AnnotationFormatError,往往因为注解类被篡改、jar 包版本混用,或用了 @Retention(RetentionPolicy.SOURCE) 却试图在运行时用反射获取。
-
@Retention决定注解存活到哪一阶段:SOURCE(仅源码)、CLASS(编译进 class 文件但不加载进 JVM)、RUNTIME(可反射读取) - 绝大多数自定义注解要生效于运行时,必须显式声明
@Retention(RetentionPolicy.RUNTIME) - 没加
@Retention的注解,默认是CLASS,反射拿不到——这是最常踩的坑
@Retention(RetentionPolicy.RUNTIME) 是反射读取的前提
想用 Class.getAnnotation()、Method.getAnnotation() 这类反射方法拿到自定义注解,@Retention 必须设为 RUNTIME。否则返回 null,且毫无警告。
使用场景举例:Spring 的 @Autowired、MyBatis 的 @Select 都依赖此设置,容器启动时扫描并处理。
立即学习“Java免费学习笔记(深入)”;
-
@Retention(RetentionPolicy.SOURCE):只对编译器有用,比如@SuppressWarnings,javac 用完即丢,class 文件里没有 -
@Retention(RetentionPolicy.CLASS):保留在 class 文件中,但 ClassLoader 不会将其加入运行时注解集合,ASM 或字节码增强工具可能用到 -
@Retention(RetentionPolicy.RUNTIME):唯一能让getAnnotation()返回非 null 的选项
元注解不止 @Retention,@Target 和 @Documented 很关键
@Retention 控制“活多久”,@Target 控制“能标在哪”。漏写 @Target 不报错,但会导致注解被误用在不支持的位置(比如标在字段上却只打算处理方法),且 IDE 无法提示校验。
性能影响很小,但语义清晰度差会直接拖慢协作效率。
-
@Target(ElementType.METHOD)表示只能用于方法;写成ElementType.TYPE就不能标在方法上了 -
@Documented让注解出现在生成的 Javadoc 中——如果你的注解是公开 API 的一部分,不加它,用户根本看不到这个注解的存在 -
@Inherited只对类继承有效(子类自动继承父类的注解),对接口实现、方法重写无效,别指望它让子类方法带上父类方法的注解
自定义注解 + 反射的实际调用链容易断在 ClassLoader
即使写了 @Retention(RUNTIME) 和 @Target,运行时仍拿不到注解?大概率是类没被当前线程的 ClassLoader 加载,或者用了模块系统(Java 9+)但没导出包。
常见错误现象:getAnnotation() 返回 null,但确认代码写了注解、也加了 RUNTIME。
- 检查是否用了自定义 ClassLoader,且未委托给 parent 加载注解类
- Spring Boot 2.6+ 默认禁用循环代理,若注解处理器依赖 AOP,可能因代理顺序导致反射时机过早
- Android 上 Dalvik/ART 对注解保留策略的支持有历史差异,
RUNTIME在某些旧版本中实际等效于CLASS
注解本身轻量,但它的生命周期、可见性、加载上下文,三者缺一不可。少一个,就查不到。


















