在 Web 开发中,前端校验是辅助,后端校验是底线。恶意请求、接口直接调用、前端校验绕过等场景,都必须依靠后端参数校验保障数据安全。 Spring Boot 集成 Hibernate Validator(JSR 380 / Bean Validation 2.0 规范的官方实现),提供声明式、零冗余的参数校验能力,无需手写 if-else 判断,即可完成参数合法性校验,是企业级开发的必备基础能力。
规范与实现
JSR 380:Java 官方的 Bean 校验规范(接口定义)
Hibernate Validator:规范的落地实现(Spring Boot 默认集成)
核心注解区分
@Valid:JSR 规范注解,支持嵌套校验,不支持分组
@Validated:Spring 封装注解,支持分组校验,是 Spring 特有的增强功能
适用场景 校验 @RequestBody、@RequestParam、@PathVariable 所有类型的入参
Spring Boot 2.x 使用 javax 包,3.x 版本使用 jakarta 包,直接引入校验 starter 即可:
<!-- Spring Boot 参数校验核心依赖(自动集成Hibernate Validator) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Web依赖(必选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>按功能分类,清晰区分使用场景,解决 99% 的校验需求:
关键区别
@NotBlank > @NotEmpty > @NotNull(严格程度)
校验姓名 / 用户名必须用 @NotBlank,纯空格会直接拦截
企业级规范
禁止直接用数据库实体类接收前端参数,创建专用DTO(数据传输对象) 做校验,解耦数据结构:
package com.example.demo.dto;
import jakarta.validation.constraints.*;
import lombok.Data;
import java.util.List;
/**
* 员工新增参数DTO(仅用于接收前端请求)
*/
@Data
public class EmployeeAddDTO {
// 姓名:非空+非纯空格
@NotBlank(message = "员工姓名不能为空")
@Size(min = 2, max = 10, message = "姓名长度必须在2-10个字符")
private String name;
// 邮箱:格式校验
@Email(message = "邮箱格式不正确")
@NotBlank(message = "邮箱不能为空")
private String email;
// 薪资:最小值限制
@Min(value = 5000, message = "薪资不能低于5000元")
private Double salary;
// 技能:集合不能为空
@NotEmpty(message = "技能列表不能为空")
private List<String> skills;
// 手机号:正则校验
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}支持 3 种入参类型 校验,用法不同,务必区分:
校验请求体
@RequestBody
使用 @Valid 触发校验,校验失败直接抛出异常:
package com.example.demo.controller;
import com.example.demo.common.Result;
import com.example.demo.dto.EmployeeAddDTO;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
/**
* 新增员工:校验@RequestBody参数
*/
@PostMapping
public Result<String> addEmployee(@Valid @RequestBody EmployeeAddDTO dto) {
// 代码走到这里 = 校验全部通过
return Result.success("员工添加成功:" + dto.getName());
}
}校验查询参数
@RequestParam/ 路径参数@PathVariable
必须在类上添加 @Validated,才能校验普通参数:
@RestController
@RequestMapping("/api/employees")
@Validated // 开启普通参数校验
public class EmployeeController {
// 校验路径变量
@GetMapping("/{id}")
public Result<String> getEmployee(
@Min(value = 1, message = "ID必须大于0") @PathVariable Integer id
) {
return Result.success("查询员工:" + id);
}
// 校验查询参数
@GetMapping("/search")
public Result<String> search(
@NotBlank(message = "关键词不能为空") @RequestParam String keyword
) {
return Result.success("搜索:" + keyword);
}
}结合之前的统一响应类 Result,替代原生 Map 返回,符合企业前后端规范:
package com.example.demo.handler;
import com.example.demo.common.Result;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 全局参数校验异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalValidationExceptionHandler {
/**
* 处理 @RequestBody 参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
// 拼接所有错误信息
String errorMsg = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ":" + error.getDefaultMessage())
.collect(Collectors.joining(";"));
log.warn("参数校验失败:{}", errorMsg);
return Result.error(400, errorMsg);
}
/**
* 处理 @RequestParam/@PathVariable 参数校验异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result<?> handleConstraintViolation(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
String errorMsg = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(";"));
log.warn("参数校验失败:{}", errorMsg);
return Result.error(400, errorMsg);
}
}解决新增 / 更新校验差异
场景:新增不需要 ID,更新必须传 ID,用分组实现一套 DTO 多场景校验
// 1. 定义分组接口
public interface AddGroup {}
public interface UpdateGroup {}
// 2. DTO中指定分组
@Data
public class EmployeeDTO {
// 更新时必填,新增时忽略
@NotNull(message = "ID不能为空", groups = UpdateGroup.class)
private Integer id;
// 新增/更新都必填
@NotBlank(message = "姓名不能为空", groups = {AddGroup.class, UpdateGroup.class})
private String name;
}
// 3. 控制器使用 @Validated 指定分组
@PostMapping
public Result<?> add(@Validated(AddGroup.class) @RequestBody EmployeeDTO dto) {}
@PutMapping
public Result<?> update(@Validated(UpdateGroup.class) @RequestBody EmployeeDTO dto) {}对象包含子对象时,用 @Valid 开启嵌套校验:
@Data
public class OrderDTO {
@NotBlank
private String orderNo;
// 嵌套校验用户信息
@Valid
@NotNull
private UserDTO user;
}校验到第一个错误就返回
默认校验所有字段,开启快速失败提升性能:
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.Validation;
import javax.validation.Validator;
@Configuration
public class ValidatorConfig {
@Bean
public Validator validator() {
return Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true) // 快速失败
.buildValidatorFactory()
.getValidator();
}
}{
"name": " ",
"email": "test",
"salary": 3000,
"skills": [],
"phone": "123456"
}{
"code": 400,
"message": "name:员工姓名不能为空;email:邮箱格式不正确;salary:薪资不能低于5000元;skills:技能列表不能为空;phone:手机号格式不正确",
"data": null
}分层校验:Controller 层做格式校验,Service 层做业务校验
DTO 专用:绝不使用数据库实体类接收前端参数
统一响应:所有校验异常返回标准化格式,前端统一处理
分组优先:新增 / 更新接口使用分组校验,复用 DTO
快速失败:生产环境开启快速失败,减少性能损耗
日志记录:校验失败仅记录警告日志,不打印堆栈
核心依赖:spring-boot-starter-validation 自动集成校验框架
核心注解:@NotBlank(字符串)、@NotEmpty(集合)、@Email、@Pattern 满足绝大多数场景
校验触发:@RequestBody 用 @Valid,普通参数用 @Validated
异常处理:全局捕获校验异常,返回标准化响应
高级能力:分组校验、嵌套校验、快速失败,适配复杂业务场景