源本科技 | 码上会

Spring Boot 参数校验

2026/03/31
5
0

引言

在 Web 开发中,前端校验是辅助,后端校验是底线。恶意请求、接口直接调用、前端校验绕过等场景,都必须依靠后端参数校验保障数据安全。 Spring Boot 集成 Hibernate ValidatorJSR 380 / Bean Validation 2.0 规范的官方实现),提供声明式、零冗余的参数校验能力,无需手写 if-else 判断,即可完成参数合法性校验,是企业级开发的必备基础能力。


基础概念

  1. 规范与实现

    • JSR 380:Java 官方的 Bean 校验规范(接口定义)

    • Hibernate Validator:规范的落地实现(Spring Boot 默认集成)

  2. 核心注解区分

    • @Valid:JSR 规范注解,支持嵌套校验,不支持分组

    • @Validated:Spring 封装注解,支持分组校验,是 Spring 特有的增强功能

  3. 适用场景 校验 @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% 的校验需求

注解

适用类型

校验规则

核心说明

@NotNull

所有类型

不能为 null

允许空字符串、空集合

@NotEmpty

字符串 / 集合 / 数组

不能为 null + 长度 >0

字符串允许纯空格

@NotBlank

字符串

不能为 null + 非纯空格

姓名 / 标题专用

@Size

字符串 / 集合

长度在指定范围

min= 最小长度, max= 最大长度

@Email

字符串

邮箱格式

支持宽松 / 严格模式

@Min / @Max

数字类型

最小值 / 最大值

整数 / 浮点数通用

@Pattern

字符串

正则表达式匹配

手机号、身份证、自定义格式

@Past

日期类型

必须是过去时间

生日、历史时间

@Future

日期类型

必须是未来时间

预约、过期时间

@AssertTrue

布尔值

必须为 true

协议勾选、状态校验

关键区别

  • @NotBlank > @NotEmpty > @NotNull(严格程度)

  • 校验姓名 / 用户名必须用 @NotBlank,纯空格会直接拦截


使用 DTO 接收参数

企业级规范

禁止直接用数据库实体类接收前端参数,创建专用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
}

最佳实践

  1. 分层校验:Controller 层做格式校验,Service 层做业务校验

  2. DTO 专用:绝不使用数据库实体类接收前端参数

  3. 统一响应:所有校验异常返回标准化格式,前端统一处理

  4. 分组优先:新增 / 更新接口使用分组校验,复用 DTO

  5. 快速失败:生产环境开启快速失败,减少性能损耗

  6. 日志记录:校验失败仅记录警告日志,不打印堆栈


总结

  1. 核心依赖spring-boot-starter-validation 自动集成校验框架

  2. 核心注解@NotBlank(字符串)、@NotEmpty(集合)、@Email@Pattern 满足绝大多数场景

  3. 校验触发@RequestBody@Valid,普通参数用 @Validated

  4. 异常处理:全局捕获校验异常,返回标准化响应

  5. 高级能力:分组校验、嵌套校验、快速失败,适配复杂业务场景