@Target 和 @Retention(RUNTIME) 是自定义校验注解生效的必要条件:@Target 规定作用位置(如 FIELD、PARAMETER),@Retention(RUNTIME) 确保运行时可通过反射读取,二者缺一将导致校验失效。

元注解不是“装饰用的标签”,而是告诉框架“这个注解该在哪儿生效、什么时候起作用”的关键指令。@Target 就是其中最基础也最容易被忽略的一环——它直接决定自定义校验注解能不能用、在哪能用、用在哪类元素上才有效。
@Target 决定了校验注解的“使用边界”
比如一个手机号校验注解 @PhoneNumber,如果只写 @Target(ElementType.FIELD),那它只能加在实体类的字段上:
- ✅ 正确:
@PhoneNumber private String phone; - ❌ 报错:加在方法参数上
public void register(@PhoneNumber String phone)(编译不通过) - ❌ 无效:加在 Controller 方法上,框架根本不会扫描,校验逻辑完全不触发
实际开发中,多数校验场景需要覆盖字段 + 方法参数(如 DTO 入参),所以更合理的写法是:
@Target({ElementType.FIELD, ElementType.PARAMETER})- 若还要支持嵌套对象校验(如 @Valid 配合级联),还得加上
ElementType.TYPE_USE
@Retention(RUNTIME) 是运行时校验的前提
校验动作发生在请求进入 Controller 后、业务逻辑执行前,框架靠反射读取注解信息。如果漏掉 @Retention(RetentionPolicy.RUNTIME),注解在运行期就“消失”了:
- 即使写了
@PhoneNumber,Validator 也拿不到任何元数据 - BindingResult 中不会出现对应错误,校验形同虚设
- 常见误区:只加了 @Target,但没加 @Retention,导致调试半天发现“注解没生效”
校验注解 ≠ 单纯标记,它是一组契约
一个可用的自定义校验注解,至少要满足三要素:
- @Target:明确作用位置(字段?参数?类型?)
- @Retention(RUNTIME):确保运行期可读取
- @Constraint(validatedBy = XxxValidator.class):绑定具体校验逻辑
三者缺一不可。少一个,要么编译失败,要么运行无效果,要么抛出 ConstraintDeclarationException 异常。
别混淆 @Valid 和 @Validated 的适用范围
它们本身也是带 @Target 的注解,且限制不同:
-
@Valid:支持FIELD、PARAMETER、TYPE_USE等,适合字段级和嵌套校验 -
@Validated:只支持TYPE、METHOD、PARAMETER,不能直接标在字段上 - 所以你的自定义注解如果想被 @Validated 触发,就必须允许
PARAMETER;如果想配合 @Valid 做级联校验,就得包含TYPE_USE

















