ElIauk

Spring Boot 参数接收与校验入门

参数从哪来 写接口第一步是搞清楚参数从请求的哪个位置取。Spring MVC 提供了一组注解,各管一块: 注解 数据来源 示例 @PathVariable 路径中的占位符 /users/{id} @RequestParam URL 查询参数或表单字段 ?page=1 @RequestBody 请求体

评论
Spring Boot 参数接收与校验入门

参数从哪来

写接口第一步是搞清楚参数从请求的哪个位置取。Spring MVC 提供了一组注解,各管一块:

注解 数据来源 示例
@PathVariable 路径中的占位符 /users/{id}
@RequestParam URL 查询参数或表单字段 ?page=1
@RequestBody 请求体,通常是 JSON POST 的 body
@RequestHeader 请求头 Authorization
@ModelAttribute 表单字段绑定到对象 表单提交

GET 请求用 @RequestParam 或 @PathVariable,POST/PUT 传 JSON 用 @RequestBody。一个方法里可以混用,但 @RequestBody 只能有一个,因为请求体只能被读一次。

日期参数建议统一用 yyyy-MM-dd 格式:query 参数在 DTO 字段上加 @DateTimeFormat(pattern = "yyyy-MM-dd"),JSON body 用 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")。不指定格式时,前端传 2022-08-08 可能直接报类型转换错误。

数组或集合参数用 @RequestParam List<Long> ids,请求写成 ?ids=1&ids=2,或者 ?ids=1,2 也能绑定。参数个数不固定时不要写一堆方法重载,直接收集合更灵活。

表单提交和 JSON 提交的校验行为不完全一样:表单对象校验失败抛 BindException,JSON body 抛 MethodArgumentNotValidException,全局异常处理里两种都要覆盖,否则总有一边返回默认错误页。

加校验注解

手工写 if (name == null || name.trim().isEmpty()) 既啰嗦又容易漏。引入校验依赖后,直接在 DTO 字段上写注解:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

注意 Spring Boot 2.3 之后校验依赖不再默认包含,只加 starter-web 时,要么编译时报找不到注解,要么校验器缺失导致注解不生效。

@Valid 加在方法参数上,校验的是这个参数对象内部的约束;@Validated 加在类上,才会启用对 @RequestParam、@PathVariable 这类方法参数的校验。两者别混。升级 Spring Boot 大版本时,校验包的命名空间会从 javax 变成 jakarta,依赖也要跟着换。

常用的几个注解:

注解 作用
@NotNull 不能为 null,可以是空字符串
@NotBlank 字符串不能为 null、不能全是空白
@NotEmpty 集合/字符串不能为 null 且长度大于 0
@Size(min, max) 长度范围
@Min / @Max 数值范围
@Email 邮箱格式
@Pattern 正则匹配
public class UserCreateDTO {
    @NotBlank(message = "用户名不能为空")
    @Size(max = 20, message = "用户名不能超过 20 个字符")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Min(value = 1, message = "年龄必须大于 0")
    private Integer age;

    // 省略 getter / setter
}

@Valid 与 @Validated

两个注解都能触发校验,区别在于:

  • @Valid 来自 JSR-303,标准注解,用在参数、字段、方法上;
  • @Validated 是 Spring 提供的,额外支持分组,还可以标在类上开启方法参数校验。

最常用的组合是:Controller 方法的 @RequestBody 参数前加 @Valid,类上加 @Validated,后者主要是为了 @RequestParam、@PathVariable 上的约束能生效。

嵌套对象要特别注意:UserCreateDTO 里如果还有一个 AddressDTO 字段,只在参数上加 @Valid 不会校验 AddressDTO 的内部字段,必须在字段上再标一个 @Valid:

public class UserCreateDTO {
    @Valid
    @NotNull(message = "地址不能为空")
    private AddressDTO address;
}

集合元素同理:List<@Valid AddressDTO> addresses 或字段上的 @Valid,光靠外层参数上的 @Valid 管不到里面。

分组校验

新增和修改往往用同一个 DTO:新增时 id 必须为空,修改时 id 必须不为空。这时用分组:

public interface Create {
}

public interface Update {
}

public class UserDTO {
    @Null(groups = Create.class, message = "新增时不能指定 id")
    @NotNull(groups = Update.class, message = "修改时必须指定 id")
    private Long id;

    @NotBlank(groups = {Create.class, Update.class}, message = "用户名不能为空")
    private String name;
}

@PostMapping
public void create(@RequestBody @Validated(Create.class) UserDTO dto) {
}

@PutMapping
public void update(@RequestBody @Validated(Update.class) UserDTO dto) {
}

分组接口就是空接口,用来当标记。不写 groups 的注解属于 Default 分组,指定了分组之后默认分组就不再校验,这一点容易踩。

常见坑

1. 校验注解完全不生效

先看依赖里有没有 spring-boot-starter-validation,再看参数前有没有 @Valid 或 @Validated。两个都没问题,再检查是不是捕获了 MethodArgumentNotValidException 却什么都没做。

2. @RequestParam 上的约束不生效

@RequestParam @Min(1) Integer page 这种写法,光有 @Valid 不够,Controller 类上必须加 @Validated,否则约束根本不会被执行。

3. 嵌套对象没加 @Valid

外层字段校验通过,内层却传了空对象也能成功。给嵌套字段补上 @Valid,别只在外层参数上标一个。

4. javax 和 jakarta 混用

Spring Boot 2.7 用的是 javax.validation.constraints.*,Spring Boot 3.0 换成了 jakarta.validation.constraints.*。从旧项目复制代码到 3.x 报“找不到包”,就是这里的原因。

5. 校验失败返回默认 400

不写全局异常处理时,校验失败返回的是 Spring 默认的错误 JSON,字段名和提示都很别扭。用 @RestControllerAdvice 统一处理 MethodArgumentNotValidException,把错误信息整理成业务统一格式。

排查步骤

  1. 确认 spring-boot-starter-validation 在依赖树里:mvn dependency:tree -Dincludes=*validation*;
  2. 确认参数前有 @Valid / @Validated,并且类上标了 @Validated;
  3. 在全局异常处理器里打印 BindingResult 的字段错误,确认校验到底跑没跑;
  4. 用 Postman 或 curl 构造边界数据,逐条验证注解的 message;
  5. 检查 javax 与 jakarta 是否和当前 Spring Boot 版本匹配。

参考

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者