spring validation 默认不递归校验嵌套对象,必须显式添加 @valid;集合校验需配合泛型声明和 @valid 标注字段;bindingresult 必须紧随对应 @valid 参数之后;自定义注解无法穿透嵌套或遍历集合,复杂场景建议手写校验。

嵌套对象校验时 @Valid 必须显式声明
Spring Validation 默认不会递归校验嵌套对象,哪怕字段加了 @NotNull 或 @Size,只要没加 @Valid,内部字段的约束就完全被忽略。
比如 User 包含 Address address,而 Address 有 @NotBlank 的 city 字段——不加 @Valid,传入 address = new Address()(city 为 null)也能通过校验。
-
Address类本身无需额外注解,但必须是独立的校验单元(即有自己的一组@NotNull、@Pattern等) -
User中的address字段必须标注@Valid,不能只靠@NotNull - 如果嵌套两层(如
User → Address → GeoLocation),每一层引用字段都要加@Valid
集合类元素校验要搭配 @Valid 和泛型约束
校验 List<address></address> 或 Set<phone></phone> 时,仅在字段上加 @Valid 不够,JVM 擦除泛型后框架无法知道集合里该校验什么类型。
常见错误:写了 @Valid List<address> addresses</address>,但 Address 内部字段仍不触发校验——本质是缺少运行时类型信息。
- 必须确保集合字段声明含具体泛型,如
List<address></address>,不能是裸类型List -
@Valid要加在字段上,不是泛型参数上(@Valid List<address></address>✅,List❌) - 若用 Lombok 的
@Data,注意它默认不生成toString()或校验相关方法,不影响@Valid行为,但调试时可能掩盖空指针
BindingResult 必须紧跟 @Valid 参数之后
Controller 方法中,如果漏掉 BindingResult 或位置不对,校验失败会直接抛 MethodArgumentNotValidException,而不是交由你处理错误信息。
典型错误写法:public String save(@Valid User user, Model model, BindingResult result) —— BindingResult 必须紧邻被校验参数,中间不能插其他参数。
- 正确顺序:
@Valid User user, BindingResult result, Model model -
BindingResult只捕获前一个@Valid参数的错误,多个校验对象需多个BindingResult(不推荐,应合并 DTO) - 调用
result.hasErrors()后,必须用result.getAllErrors()或result.getFieldErrors("xxx")显式取错,不能依赖日志自动输出
自定义校验注解对嵌套/集合的支持有限
像 @Email、@Past 这类内置注解能自然作用于嵌套字段或集合元素,但自定义注解(如 @PasswordMatches)默认只校验当前对象层级,无法穿透到子对象或遍历集合。
例如想校验 User 中所有 Address 的 postalCode 是否符合本国格式,不能靠单个 @Valid + 自定义注解实现。
- 自定义注解的
ConstraintValidator接收的是被注解字段的值(如单个Address),不是整个List<address></address> - 若需跨层级逻辑,得改用
@ScriptAssert(Groovy/JS)或在 Service 层手动遍历校验 - 避免在自定义注解里调用
Validator.validate()递归校验,容易引发栈溢出或循环依赖
嵌套深、集合多的时候,校验逻辑很快会脱离注解能优雅表达的范围,这时候手写校验代码反而更可控、更易测试。











