参数校验
接口入参必须可信:空值、超长、非法格式要在入口处拦截。Spring Boot 整合 Bean Validation,用声明式注解完成校验,无需手写一堆 if。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
该 starter 自带 jakarta.validation(Bean Validation)API 与 Hibernate Validator 实现。注意:Boot 3 起包名全部是 jakarta.validation.*(javax.validation.* 只存在于 2.x)。
请求体校验
定义请求对象(record 更简洁,普通类亦可):
public record CreateUserRequest(
@NotBlank @Size(max = 20) String name,
@Min(1) @Max(150) int age,
@Email String email
) {}
控制器加 @Valid,校验失败时 Spring MVC 抛 MethodArgumentNotValidException,默认响应 400:
@PostMapping("/users")
public User create(@Valid @RequestBody CreateUserRequest req) {
return userService.create(req);
}
常用约束注解
| 注解 | 作用 | 适用 |
|---|---|---|
| @NotNull | 不能为 null | 任意对象 |
| @NotEmpty | 非 null 且非空 | 字符串/集合 |
| @NotBlank | 非空白串 | 字符串 |
| @Size(min, max) | 长度/个数范围 | 字符串/集合/数组 |
| @Min / @Max | 数值上下限 | 数值 |
| @Pattern(regexp) | 正则匹配 | 字符串 |
| 邮箱格式 | 字符串 |
以上均在 jakarta.validation.constraints 包下。
路径与查询参数校验
控制器类上加 @Validated,参数上直接放约束,失败抛 ConstraintViolationException:
@Validated
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User get(@PathVariable @Min(1) long id,
@RequestParam(defaultValue = "0") @Min(0) int page) {
return userService.findById(id);
}
}
分组校验
同一对象在不同接口规则不同(新建必填 name、更新可缺省),用「分组接口」区分:
public interface Create {}
public interface Update {}
public class SaveUserRequest {
@NotBlank(groups = Create.class)
private String name;
@Email(groups = {Create.class, Update.class})
private String email;
}
@PostMapping("/users")
public void create(@Validated(Create.class) @RequestBody SaveUserRequest req) { }
@PutMapping("/users/{id}")
public void update(@PathVariable long id,
@Validated(Update.class) @RequestBody SaveUserRequest req) { }
未写 groups 的约束属于默认 Default 组,校验指定分组时默认组约束不会执行;想让默认组与自定义组一起生效,可让分组接口 extends Default,细节以官方文档为准。
统一异常处理
用 @RestControllerAdvice 把校验错误转成统一结构:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, Object> handleBody(MethodArgumentNotValidException ex) {
String msg = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.joining("; "));
return Map.of("code", 400, "message", msg);
}
@ExceptionHandler(ConstraintViolationException.class)
public Map<String, Object> handleParam(ConstraintViolationException ex) {
return Map.of("code", 400, "message", ex.getMessage());
}
}
小结
总结三步:DTO 上声明式加约束 → 入口用 @Valid/@Validated → 全局异常处理统一返回格式。嵌套校验记得给对象字段加 @Valid。分组校验解决「一个对象多种规则」,细节以官方文档为准。