Java注解是继承Annotation接口的元数据标签,用于为程序元素添加说明信息而不影响逻辑;需用@Retention(RUNTIME)和@Target明确生命周期与作用位置才能通过反射读取。

Annotation 是 Java 中的元数据,本质是一个继承 java.lang.annotation.Annotation 的特殊接口,用来为类、方法、字段等程序元素附加说明性信息,本身不改变代码逻辑,也不执行任何行为——它只是“标签”,真正起作用的是读取它的工具或框架(比如 javac 编译器、JUnit 测试引擎、Spring 容器)。
@Retention 控制注解能活到什么时候
注解不是写上去就一直可用的。它的“存活时间”由 @Retention 决定,这个元注解必须显式声明,否则默认是 RetentionPolicy.CLASS,运行时反射拿不到。
-
SOURCE:只在源码阶段存在,编译完就丢,比如
@SuppressWarnings。javac 用完即弃,.class 文件里没有。 - CLASS:编译后保留在 .class 文件中,但 JVM 加载类时不加载进内存,反射无法获取。适合字节码处理工具(如 ASM)使用。
-
RUNTIME:唯一能让
getAnnotation()返回非 null 的选项。注解会被加载进 JVM,运行时可通过反射读取,Spring 的@Autowired、自定义权限校验注解都依赖它。
@Target 限定注解能贴在哪儿
光有生命周期还不够,还得规定它能标注在哪种结构上,否则可能误标在字段上却只打算处理方法,IDE 也无法提示错误。
- 常用值:
ElementType.METHOD(方法)、ElementType.TYPE(类/接口)、ElementType.FIELD(字段)、ElementType.PARAMETER(参数)等。 - 可以组合多个,比如
@Target({METHOD, TYPE})。 - 不写
@Target不报错,但等于开放所有位置(除类型参数外),容易引发误用。
一个能真正运行起来的自定义注解示例
下面这个注解声明后,你才能在运行时通过反射拿到它:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {
String value() default "default";
}
关键点:
- 必须有
@Retention(RUNTIME),否则method.getAnnotation(LogExecutionTime.class)永远返回null,且不报错、无警告。 - 加上
@Target(METHOD),编译器就会阻止你把它写在类或字段上。 - 注解里的
value()是方法签名,不是字段;调用时写@LogExecutionTime("api")就是给它传参。
为什么常踩坑?因为反射读不到 ≠ 注解没写对
很多开发者写了注解,也加了 @Target,但忘了 @Retention(RUNTIME),结果反射始终为空。这不是 bug,是设计如此:JVM 明确按生命周期过滤,不会“尽力而为”。
- 检查方式:反编译 .class 文件(
javap -v YourClass.class),看 Constant Pool 里有没有你的注解名;或者直接调试clazz.getAnnotations()看长度。 - 常见报错
AnnotationFormatError,往往是因为 jar 包混用、注解类被篡改,或用了 SOURCE/CLASS 却硬要反射读取。

















