要真正用好java注解必须配对使用@target和@retention元注解,前者限定作用位置(如method、field),后者决定生命周期(仅runtime支持反射获取),缺一不可。

要真正用好 Java 注解,必须吃透元注解——它们不是装饰,而是注解的“控制开关”。没配对的 @Target 和 @Retention,自定义注解就只是摆设;写错 ElementType 或 RetentionPolicy,反射拿不到、编译器报错、文档不生成,全都会出问题。
@Target:精准限定注解能贴在哪儿
它决定注解的“使用权限”,不声明或声明错误,编译器直接拒绝。默认只允许加在类/接口上(ElementType.TYPE),但业务中绝大多数注解需要作用于方法、参数甚至泛型位置。
- METHOD:最常用,比如日志、权限、事务类注解都标在方法上
- FIELD:用于字段校验(如 @NotNull)、序列化控制(如 @JsonIgnore)
- PARAMETER:配合 AOP 或参数校验框架,拦截入参做统一处理
- TYPE_PARAMETER:JDK 8+ 支持,可标注泛型类型(如 )
- TYPE_USE:作用范围最广,能标在任意类型使用处(如 List),但需搭配 RUNTIME 才能在运行时通过 AnnotatedType 获取
多个位置用数组写法:@Target({METHOD, FIELD, PARAMETER})。漏掉关键目标(比如想注解参数却没写 PARAMETER),编译时报错:“Cannot use @MyAnno on this element”。
@Retention:决定注解“活到什么时候”
这是反射能否读取注解的生死线。SOURCE 只给编译器看,CLASS 写进字节码但 JVM 不加载,只有 RUNTIME 才能让 getAnnotation() 返回非 null。
- SOURCE:适合编译期检查,如 @Override、@SuppressWarnings,编译完即丢弃
- CLASS:Lombok 多数注解走这条路,javac 生成代码后注解就消失,不进 JVM
- RUNTIME:Spring、JUnit、自定义拦截器依赖它,必须显式声明,不能省略
没写 @Retention,默认是 CLASS——这意味着即使你写了 @Target(METHOD),反射也拿不到,因为 JVM 根本没把它加载进来。局部变量(LOCAL_VARIABLE)即使配了 RUNTIME,反射也无法获取,这是 JVM 规范限制,不是代码问题。
其他元注解:按需启用的关键能力
@Documented 和 @Inherited 不影响功能逻辑,但影响协作效率和继承行为。
- @Documented:加了它,Javadoc 会把注解本身及其属性一起生成进 API 文档,方便团队理解语义
- @Inherited:仅对 TYPE 生效(类上标注),子类自动继承父类的该注解;对 METHOD、FIELD 无效
- @Repeatable:让同一位置能重复使用同一个注解(如 @RolesAllowed("ADMIN") @RolesAllowed("USER")),需配套定义容器注解
组合注解(如 Spring 的 @RestController)本质就是多个元注解叠加 + 属性桥接(@AliasFor),把分散配置收拢成一个高语义标签,既防遗漏,又提升可读性。
最小可用自定义注解模板
一个能真正运行起来的注解,至少包含两个元注解,且目标与生命周期必须匹配:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
String value() default "default";
}
然后就能在方法上使用:@LogExecutionTime("user-service") public void doWork() { … },再通过 Method.getAnnotation(LogExecutionTime.class) 取值做后续处理。少一个元注解,或 ElementType 与实际使用位置不符,整个链路就断了。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











