bigywater.tech
Spring · 45

数据校验:@Valid、@Validated 与分组

@Valid 和 @Validated 有什么区别?为什么我写了 @NotBlank 却没生效?分组到底查哪些约束?嵌套对象怎么校验?校验失败时客户端能看到什么?这篇在 Spring Boot 4.0.8 上逐个测量。

3 个违规
带 @Valid 的请求体一次报出全部违规;不写 @Valid 一个都不报
400 → 500
控制器类上加 @Validated 之后,同样的参数校验失败的状态码
只报 1 个
@Validated(Create.class) 时 name、age 的默认组约束没有检查

请求体

实测 · 本机执行输出
  POST /v/body {"name": "Tom", "age": 20, "addr": {"city": "BJ"}}→ 200 body User[id=null, name=Tom, age=20, addr=Address[city=BJ]]
         Controller 方法被调用:body User[id=null, name=Tom, age=20, addr=Address[city=BJ]]
  POST /v/body {"name": " ", "age": 10, "addr": {"city": ""}}→ 400 [MethodArgumentNotValidException] addr.city=city 不能为空; age=age 至少 18; name=name 不能为空
  POST /v/body-nobind {"name": " ", "age": 10, "addr": {"city": ""}}→ 200 bodyNo(没有 @Valid) User[id=null, name= , age=10, addr=Address[city=]]
         Controller 方法被调用:bodyNo(没有 @Valid) User[id=null, name= , age=10, addr=Address[city=]]
  POST /v/body-binding {"name": " ", "age": 10, "addr": {"city": ""}}→ 200 BindingResult 错误数=3 [addr.city, age, name]
         Controller 方法被调用:BindingResult 错误数=3 [addr.city, age, name]

@Valid 触发校验,失败抛异常,Controller 不被调用;BindingResult 可以自己处理。

参数上的约束

实测 · 本机执行输出
  GET /v/param?page=0&q=%20 → 400 [HandlerMethodValidationException] page 至少 1; q 不能为空
  GET /v/path/0 → 400 [HandlerMethodValidationException] id 至少 1
  GET /vc/param?page=0 → 500 [ConstraintViolationException] page 至少 1
  GET /v/svc?n=0 → 500 [ConstraintViolationException] n 至少 1
  GET /v/plain?n=0 → 200 plain ok 0
         Controller 方法被调用:plain ok 0

内置方法校验返回 400;控制器类加 @Validated 会走 AOP 变成 500;Service 必须类上有 @Validated。

分组

实测 · 本机执行输出
  POST /v/g-create {"id": 5, "name": "", "age": 1}→ 400 [MethodArgumentNotValidException] id=创建时 id 必须为空
  POST /v/g-create {"name": "", "age": 1}→ 200 create User[id=null, name=, age=1, addr=null]
         Controller 方法被调用:create User[id=null, name=, age=1, addr=null]
  POST /v/g-update {"name": "", "age": 1}→ 400 [MethodArgumentNotValidException] id=更新时 id 不能为空
  POST /v/g-both {"id": 5, "name": "", "age": 1}→ 400 [MethodArgumentNotValidException] age=age 至少 18; id=创建时 id 必须为空; name=name 不能为空

指定分组后默认组不参加,漏掉的约束会静默失效。

嵌套

实测 · 本机执行输出
  POST /v/nested1 {"no": "a", "addr": {"city": ""}}→ 200 nested1 Order1[no=a, addr=Address[city=]]
         Controller 方法被调用:nested1 Order1[no=a, addr=Address[city=]]
  POST /v/nested2 {"no": "a", "addr": {"city": ""}}→ 400 [MethodArgumentNotValidException] addr.city=city 不能为空

每一层嵌套都要写 @Valid。

默认响应

实测 · 本机执行输出
  POST /v/body {"name": " ", "age": 10, "addr": {"city": ""}}→ 400 {"status":400,"error":"Bad Request","path":"/v/body"}
  GET /v/param?page=0&q=%20 → 400 {"status":400,"error":"Bad Request","path":"/v/param"}
  GET /vc/param?page=0 → 500 {"status":500,"error":"Internal Server Error","path":"/vc/param"}
  GET /v/svc?n=0 → 500 {"status":500,"error":"Internal Server Error","path":"/v/svc"}

Boot 默认不带违规信息。

ProblemDetail

实测 · 本机执行输出
  POST /v/body {"name": " ", "age": 10, "addr": {"city": ""}}→ 400 {"detail":"Invalid request content.","instance":"/v/body","status":400,"title":"Bad Request"}
  GET /v/param?page=0&q=%20 → 400 {"detail":"Validation failure","instance":"/v/param","status":400,"title":"Bad Request"}

开关只给概括,字段级信息要自己放进响应。

分步演示

数据校验
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用 Spring Boot 4.0.8 的 SpringApplication(spring-boot-starter-webmvc + spring-boot-starter-validation,内嵌 Tomcat 11);异常类型与违规消息由测试用的 @RestControllerAdvice 打印(按属性开关),约束消息用 message 属性固定为中文;响应里的 timestamp 在输出时被略去
    • 没有验证:自定义约束注解与 ConstraintValidator 注入 Spring Bean;集合元素的容器元素校验(List<@NotBlank String>);@Validated 作用在 @ConfigurationProperties 上;校验消息的国际化(MessageSource);fail-fast 配置;WebFlux 里的校验;records 以外的普通类(getter、setter)上约束的差别
    京ICP备2026067502号-1京公网安备11010502063487号