@Target和@Retention是保障自定义注解生效的双重防线:@Target限定注解可用位置(如METHOD、PARAMETER),编译期即拦截误用;@Retention指定生命周期,校验类必须设为RUNTIME才能被反射读取。
@target 和 @retention 是防止自定义注解被误用的两道关键防线。光有校验逻辑,注解贴错地方或运行时“看不见”,整个验证就失效了。核心不是靠人盯,而是靠编译器和 jvm 在不同阶段主动拦截。
Target 控制“能贴在哪”,编译期就卡死错误位置
@Target 明确限定注解可用的语法元素类型。一旦声明,Java 编译器(javac)会在编译时严格检查——贴错位置直接报错,不给运行机会。
- 比如定义为 @Target(ElementType.FIELD),却用在方法上:
error: annotation @State is not applicable to this kind of declaration - 若漏写 @Target,默认等价于 @Target(ElementType.ANNOTATION_TYPE),即只能标注其他注解——这几乎不是业务场景想要的,但编译器不会提醒你漏了,隐患极大
- 常见合理组合:字段+参数(
FIELD, PARAMETER)适合校验入参;方法+类型(METHOD, TYPE)适合 AOP 切面;仅TYPE适合模块标记
Retention 控制“能活到哪”,避免运行时反射取不到
@Retention 决定注解信息是否能撑到运行期。大多数校验依赖反射读取注解,必须选 RetentionPolicy.RUNTIME;否则即使位置贴对了,getAnnotation() 也返回 null。
-
SOURCE:只存在于 .java 文件,编译后消失 → 适合给编译器看(如
@Override),不适合运行时校验 - CLASS:保留在 .class 文件里,但 JVM 不加载进内存 → 反射读不到,容易误以为“注解丢了”
-
RUNTIME:JVM 加载类时一并加载注解 →
field.getAnnotation(State.class)才能拿到实例
两者配合不当,校验逻辑再正确也白搭
单独写对一个不够,必须协同生效:
- 写了
@Retention(RUNTIME)但@Target没包含实际使用位置 → 编译失败,根本跑不起来 - 写了
@Target({FIELD, METHOD})但@Retention(SOURCE)→ 编译通过,运行时反射为空,校验静默失效 - Spring Boot 的
@Validated或 Hibernate Validator 要求注解必须是 RUNTIME + 正确 TARGET,否则字段校验直接跳过
IDE 和构建工具的检查差异要留意
IntelliJ 对 @Target 的匹配检查较宽松,尤其 Kotlin/Java 混合项目中可能“看起来正常”;而 Maven 的 maven-compiler-plugin 会严格执行。这意味着:
- 本地 IDE 不报错 ≠ 编译通过
- CI 流水线编译失败,往往就是 @Target 配置窄了(比如忘了加 PARAMETER)或漏写了
- 建议在 pom.xml 中启用更严格的编译选项,或在 CI 阶段统一用 javac 验证










