@Target指定注解适用位置,@Retention控制保留阶段;必须显式声明二者,否则易致反射失效或编译报错。

Target 指定注解能加在哪些地方
注解不是随便往哪都能贴的——@Target 就是干这个事的。它用 ElementType 枚举值控制生效位置,比如你写了个校验注解,只允许用在字段上,那必须显式声明 @Target(ElementType.FIELD),否则 IDE 可能不报错,但运行时反射拿不到,或者被误用在方法上导致逻辑错乱。
常见组合示例:
-
@Target({ElementType.METHOD, ElementType.FIELD}):既可用于 getter 方法,也可用于实体字段 -
@Target(ElementType.TYPE):只能加在类、接口、枚举上(比如标记某个模块的入口) -
@Target(ElementType.PARAMETER):专用于方法参数(常配合 AOP 或参数解析器)
漏写 @Target 不会编译失败,但默认等价于 @Target(ElementType.ANNOTATION_TYPE)(即只能标注其他注解),这几乎不是你想要的。
Retention 控制注解保留到哪个阶段
@Retention 决定注解“活多久”:源码期(SOURCE)、编译期(CLASS)、运行期(RUNTIME)。绝大多数自定义注解需要反射读取,必须选 RetentionPolicy.RUNTIME;如果只是给编译器看(比如 @Override),用 SOURCE 就够了。
容易踩的坑:
- 写成
@Retention(RetentionPolicy.CLASS)却在运行时用getAnnotation()去取——返回null,因为字节码里虽有,但 JVM 不加载进内存 - 误以为
SOURCE能被注解处理器(APT)处理,其实可以,但注意:Kotlin 的 kapt 默认不支持 Java APT 的SOURCE注解,得配kapt.includeCompileClasspath = true
两者搭配不当会导致注解“看不见”
哪怕 @Retention(RetentionPolicy.RUNTIME) 写对了,如果 @Target 没覆盖实际使用位置,编译器就直接报错。比如定义了 @Target(ElementType.METHOD),却用在字段上:javac 会提示 error: annotation @XXX is not applicable to this kind of declaration。
典型错误场景:
- 想用注解驱动 JSON 序列化(如
@JsonAlias风格),但忘了加FIELD和METHOD,结果 setter/getter 上的注解被忽略 - 写了个日志切面注解,
@Target只写了TYPE,结果加在方法上不生效,还以为是 AOP 配置错了 - 用 Lombok 的
@FieldNameConstants时,发现生成的静态字段名不对——其实是@Target限制了只能用于TYPE,你却试图在内部类里用,编译不过
IDE 和构建工具对元注解的检查很松
IntelliJ 默认不会严格校验 @Target 是否匹配实际用法(尤其 Kotlin + Java 混合项目),而 Maven 编译(maven-compiler-plugin)会。这意味着:代码在 IDE 里绿灯,一跑 mvn compile 就挂。
建议做法:
- 所有自定义注解都显式写全
@Target和@Retention,别依赖默认 - 在单元测试里用
Class.getDeclaredField()或Method.getAnnotation()实测能否取到,而不是只看编译是否通过 - 如果用 Gradle,确认
javaToolchain版本与注解处理器兼容(例如 JDK 17+ 的RetentionPolicy.SOURCE注解,某些老版 processor 会跳过)
最麻烦的情况是:注解本身合法,但目标元素被泛型擦除、代理类包裹或模块封装(如 JPMS),这时候 getAnnotation() 返回 null 往往不是元注解问题,而是反射路径没走对——得先排除 @Target 和 @Retention 配置错误,再查更底层的机制。










