Spring Boot实现参数校验案例:从入门到进阶的完整实践指南
目录导读
- 为什么需要参数校验? —— 后端安全的“第一道防线”
- 基础环境搭建 —— 依赖引入与注解初识
- 核心注解详解 —— @Validated vs @Valid,@NotNull/@NotBlank/@Size等实战
- 全局异常处理 —— 优雅捕获校验错误,统一返回格式
- 分组校验与自定义校验器 —— 解决复杂业务场景
- 常见问题与问答精选 —— 开发者最关心的10个坑
- 性能优化与最佳实践
为什么需要参数校验?
在开发RESTful API时,前端传参不可控(如空值、超长字符串、非法格式),若后端不校验,轻则产生脏数据,重则导致SQL注入或系统崩溃,Spring Boot通过Java Bean Validation(JSR-380)规范,配合Hibernate Validator,能以声明式注解快速完成校验,大幅减少冗余代码。

基础环境搭建
在pom.xml中添加(Spring Boot 2.3+已内置):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
实体类示例(用户注册DTO):
public class UserDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度需3-20字符")
private String username;
@Email(message = "邮箱格式错误")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误")
private String phone;
}
核心注解详解
@Validated vs @Valid
- @Valid(javax.validation):用于方法参数、属性,触发级联校验
- @Validated(Spring):支持分组校验,用在Controller类上更灵活
常用校验注解速查
| 注解 | 用途 | 典型场景 |
|---|---|---|
| @NotNull | 对象非空 | 数值、引用类型 |
| @NotBlank | 字符串非空(去除空白后) | 用户名、密码 |
| @NotEmpty | 集合/字符串非空 | List、Map |
| @Size | 长度范围 | 字符串长度限制 |
| @Min / @Max | 数值范围 | 年龄、价格 |
| @Positive | 正数 | 数量 |
| @Past / @Future | 日期校验 | 生日、活动时间 |
| @Pattern | 正则表达式 | 手机号、身份证号 |
全局异常处理(核心亮点)
单独校验失败会抛出MethodArgumentNotValidException,需要统一处理:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Result> handleValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
return ResponseEntity.badRequest().body(Result.error(400, message));
}
}
还可以配合@ControllerAdvice处理ConstraintViolationException(路径变量校验)和BindException(表单绑定校验)。
分组校验与自定义校验器
场景:同一个DTO在不同接口(新增/修改)有不同的校验规则
public interface AddGroup {}
public interface UpdateGroup {}
public class UserDTO {
@Null(groups = AddGroup.class, message = "新增时ID必须为空")
@NotNull(groups = UpdateGroup.class, message = "修改时ID不能为空")
private Long id;
}
// Controller中使用
@PostMapping("/user")
public Result addUser(@Validated(AddGroup.class) @RequestBody UserDTO dto) { ... }
自定义注解(验证枚举值):
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = GenderValidator.class)
public @interface Gender {
String message() default "性别只能为男或女";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class GenderValidator implements ConstraintValidator<Gender, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return "男".equals(value) || "女".equals(value);
}
}
常见问题与问答精选
Q1:为什么我在Controller里用@Valid没生效?
A:检查是否引入spring-boot-starter-validation依赖(Spring Boot 2.3+需显式引入),Controller类上需加@Validated才能校验路径变量和请求参数。
Q2:如何校验List集合中的每个元素?
A:使用@Valid注解在List属性上(如@Valid List<OrderItem>),同时OrderItem类内部字段加校验注解。
Q3:校验失败的消息能否支持国际化?
A:可以,在resources下创建ValidationMessages.properties,并用{code}占位符,如@NotNull(message = "{user.name.notnull}")。
Q4:性能优化方面有何技巧?
A:对于高并发接口,建议去掉@Valid的深校验,改为在Service层手动校验关键字段;使用@Profile在测试环境打印详细错误,生产环境只返回简洁提示。
最佳实践总结
- Controller层只做参数校验,Service层处理业务异常,避免校验逻辑分散。
- 避免过度校验:只校验非空、长度、格式,业务状态(如用户是否存在)留给Service。
- 使用DTO而非实体类:防止前端传入敏感字段(如密码哈希)。
- 日志记录:在全局异常处理器中打印参数详情,便于排查。
Spring Boot的参数校验远不止注解这么简单,结合分组和自定义扩展,能覆盖99%的业务场景,建议开发者掌握以上案例后,在实际项目中逐步沉淀自己的校验规范,若遇到特殊场景,可查阅Hibernate Validator官方文档进一步探索。