Spring Boot 3.x + JDK17 参数校验全场景实战
在日常开发中,接口参数校验是保障数据合法性、减少业务异常的关键环节。如果用传统的if-else逐一判断,不仅代码冗余、可读性差,还难以维护。Spring Boot 3.x 整合 Spring Validation(基于JSR-380规范,底层依赖Hibernate Validator 8.x,适配JDK17),提供注解驱动的校验方式,可快速适配单参数、实体类、List列表、嵌套对象等多场景,搭配全局异常处理与自定义校验,轻松搭建标准化校验体系。
本文适配JDK17+SpringBoot3.x环境,覆盖参数校验全场景,重点细化分组校验、完善Service层校验,附完整可复用代码,新手也能直接上手。
一、环境准备(Maven依赖,适配SpringBoot3.x+JDK17)
Spring Boot 3.x 需显式引入校验 starter,依赖适配JDK17的Hibernate Validator 8.x(无需手动指定版本,Spring Boot父工程已管理),Maven配置如下:
xml
<!-- 参数校验核心依赖,SpringBoot3.x 适配JDK17 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 可选:用于返回标准化结果,可替换为自身项目的响应封装 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
注意:SpringBoot3.x 要求 Hibernate Validator 最低版本为8.x,该版本兼容JDK11+,完美适配JDK17;若手动指定版本,需避免版本冲突(推荐使用Spring Boot父工程管理的版本)。
二、核心校验注解(必记)
先掌握常用注解,覆盖绝大多数基础校验场景,后续所有实战都基于这些注解扩展,适配JDK17+SpringBoot3.x无差异:
| 注解 | 作用 | 适用类型 |
|---|---|---|
| @NotNull | 非空(仅判断是否为null,不判断空字符串/空集合) | 所有引用类型(String、Integer、List等) |
| @NotBlank | 非空且去空格后长度>0(仅针对字符串) | String |
| @NotEmpty | 非空且元素个数>0(字符串/集合/数组) | String、List、数组 |
| @Min(value) | 数值不小于指定值 | Integer、Long、BigDecimal等数字类型 |
| @Max(value) | 数值不大于指定值 | 数字类型 |
| @Size(min,max) | 长度/个数在指定范围 | String、List、数组 |
| 符合邮箱格式 | String | |
| @Pattern(regexp) | 匹配指定正则表达式 | String |
三、基础校验场景(单参数+普通实体)
3.1 单参数校验(Controller层)
适用于接口参数较少(1-2个)的场景,需在Controller类上添加@Validated注解,开启方法参数校验,直接在参数上添加校验注解即可,适配JDK17+SpringBoot3.x无差异。
java
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
@RestController
@RequestMapping("/user")
@Validated // 开启单参数校验,必须加在类上
public class UserController {
// 路径参数校验(ID≥1)
@GetMapping("/{id}")
public String getUserById(@PathVariable @Min(1) Long id) {
return "查询用户:" + id;
}
// 请求参数校验(用户名非空)
@GetMapping("/query")
public String queryUser(@RequestParam @NotBlank(message = "用户名不能为空") String username) {
return "查询用户:" + username;
 }
}
3.2 普通实体类校验(最常用)
适用于接口参数较多的场景,将参数封装为DTO实体类,给字段添加校验注解,Controller入参用@Valid触发校验。
步骤1:定义DTO实体类
java
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data // Lombok简化get/set,可手动实现,适配JDK17
public class UserDTO {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度为2-20位")
private String username;
@NotNull(message = "年龄不能为空")
@Min(1)
@Max(150)
private Integer age;
@Email(message = "邮箱格式错误")
private String email;
}
步骤2:Controller触发校验
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/user")
public class UserController {
// 实体类参数校验,@Valid触发校验
@PostMapping("/create")
public Result<?> createUser(@RequestBody @Valid UserDTO userDTO) {
// 业务逻辑:调用Service保存用户
return Result.success("用户创建成功");
}
}
四、全局异常处理(友好响应,适配SpringBoot3.x)
参数校验失败会抛出两种核心异常,需通过全局异常处理器捕获,返回标准化响应(避免直接返回默认错误信息,提升接口友好度)。SpringBoot3.x 中异常处理逻辑无差异,仅需确保依赖适配。
先定义统一响应类Result:
java
import lombok.Data;
import org.springframework.http.HttpStatus;
@Data
public class Result<T> {
// 状态码:200成功,400参数错误,500系统错误
private Integer code;
// 响应信息
private String msg;
// 响应数据
private T data;
// 成功响应(无数据)
public static <T> Result<T> success(String msg) {
Result<T> result = new Result<>();
result.setCode(HttpStatus.OK.value());
result.setMsg(msg);
return result;
}
// 成功响应(带数据)
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(HttpStatus.OK.value());
result.setMsg("操作成功");
result.setData(data);
return result;
}
// 失败响应
public static <T> Result<T> fail(Integer code, String msg) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
再编写全局异常处理器:
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;
import java.util.stream.Collectors;
@RestControllerAdvice // 全局异常处理,作用于所有@RestController
@Slf4j
public class GlobalExceptionHandler {
// 处理实体类参数校验异常(@RequestBody + @Valid)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleValidException(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
// 获取第一个错误信息(也可收集所有错误信息)
String msg = bindingResult.getFieldError().getDefaultMessage();
log.error("参数校验失败:{}", msg);
return Result.fail(HttpStatus.BAD_REQUEST.value(), msg);
}
// 处理单参数校验异常(@Validated 方法参数)
@ExceptionHandler(ConstraintViolationException.class)
public Result<?> handleConstraintException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
// 收集所有错误信息(拼接为字符串)
String msg = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(","));
log.error("参数校验失败:{}", msg);
return Result.fail(HttpStatus.BAD_REQUEST.value(), msg);
}
}
五、重点:List列表参数校验(多场景适配)
开发中常遇到"实体类包含List集合"的场景(如订单包含多个商品、批量新增用户),核心要点:在List字段上添加@Valid注解,触发集合内元素的逐个校验,配合集合本身的非空/长度注解,实现双层校验,适配JDK17+SpringBoot3.x无差异。
5.1 场景1:实体类包含List<对象>(最常用)
步骤1:定义List元素的实体类(如商品DTO)
java
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
@Data
public class ProductDTO {
@NotBlank(message = "商品名称不能为空")
@Size(min = 2, max = 50, message = "商品名称长度2-50位")
private String productName;
@NotNull(message = "商品价格不能为空")
@Min(value = 1, message = "商品价格不能小于1")
private BigDecimal price;
@NotNull(message = "购买数量不能为空")
@Min(value = 1, message = "购买数量不能小于1")
private Integer num;
}
步骤2:定义包含List的主实体类(如订单DTO)
List字段需添加两层注解:① @Valid:触发集合内每个元素的校验;② @NotEmpty/@Size:校验集合本身。
java
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.List;
@Data
public class OrderDTO {
@NotBlank(message = "订单号不能为空")
private String orderNo;
@NotBlank(message = "用户ID不能为空")
private String userId;
// 核心:@Valid 触发List内每个ProductDTO的校验
// @NotEmpty 校验List非空,@Size 限制元素个数
@Valid
@NotEmpty(message = "商品列表不能为空")
@Size(min = 1, max = 10, message = "商品数量不能超过10个")
private List<ProductDTO> productList;
}
步骤3:Controller触发校验
与普通实体校验一致,只需给主实体加@Valid即可,无需额外配置。
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/order")
public class OrderController {
@PostMapping("/create")
public Result<?> createOrder(@RequestBody @Valid OrderDTO orderDTO) {
// 业务逻辑:保存订单及关联商品
return Result.success("订单创建成功");
}
}
5.2 场景2:嵌套列表校验(List<List<对象>>)
如果是二维列表(如批次订单包含多个订单,每个订单包含多个商品),只需在每一层List字段上都添加@Valid注解,实现递归全链路校验。
java
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
// 最外层:批次订单DTO
@Data
public class BatchOrderDTO {
@NotBlank(message = "批次号不能为空")
private String batchNo;
// 第一层List:@Valid触发内层OrderItemDTO的校验
@Valid
@NotEmpty(message = "订单列表不能为空")
private List<OrderItemDTO> orderItemList;
}
// 中间层:订单项DTO
@Data
public class OrderItemDTO {
@NotBlank(message = "子订单号不能为空")
private String subOrderNo;
// 第二层List:@Valid触发ProductDTO的校验
@Valid
@NotEmpty(message = "商品列表不能为空")
private List<ProductDTO> productList;
}
5.3 场景3:List<基础类型>校验(List/List)
基础类型(String、Long等)无法直接给字段加注解,需通过@Pattern或自定义注解实现校验。
方式1:@Pattern + 正则(适合简单规则)
直接在List字段上加@Pattern,正则会匹配集合中的每个元素,配合@NotEmpty校验集合非空。
java
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.util.List;
@Data
public class UserBatchDTO {
@NotBlank(message = "操作人ID不能为空")
private String operatorId;
// 校验:手机号列表非空,且每个手机号符合格式
@Valid
@NotEmpty(message = "手机号列表不能为空")
@Pattern(regexp = "1[3-9]\\d{9}", message = "手机号格式错误")
private List<String> phoneList;
// 校验:ID列表非空,且每个ID为正整数
@Valid
@NotEmpty(message = "ID列表不能为空")
@Pattern(regexp = "^[1-9]\\d*$", message = "ID必须为正整数")
private List<String> idList;
}
方式2:自定义注解(适合复杂规则,推荐)
如果校验规则复杂(如身份证、银行卡号),自定义注解更灵活,示例实现"手机号列表校验"。
步骤1:定义自定义注解
java
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER}) // 作用于字段/参数
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Constraint(validatedBy = PhoneListValidator.class) // 绑定校验器
public @interface PhoneList {
// 校验失败提示信息
String message() default "手机号列表格式错误";
// 分组校验相关,默认空
Class<?>[] groups() default {};
// 负载信息,默认空
Class<? extends Payload>[] payload() default {};
}
步骤2:实现校验器
java
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;
import java.util.regex.Pattern;
// 泛型:第一个是自定义注解,第二个是要校验的类型(List<String>)
public class PhoneListValidator implements ConstraintValidator<PhoneList, List<String>> {
// 手机号正则表达式
private static final Pattern PHONE_PATTERN = Pattern.compile("1[3-9]\\d{9}");
@Override
public boolean isValid(List<String> phoneList, ConstraintValidatorContext context) {
// 1. 列表为null/空,返回false(可配合@NotEmpty双重保障)
if (phoneList == null || phoneList.isEmpty()) {
return false;
}
// 2. 遍历每个手机号,校验格式
for (String phone : phoneList) {
if (phone == null || !PHONE_PATTERN.matcher(phone).matches()) {
return false;
}
}
return true;
}
}
步骤3:使用自定义注解
java
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class UserBatchDTO {
@NotBlank(message = "操作人ID不能为空")
private String operatorId;
// 直接使用自定义注解,简洁高效
@PhoneList(message = "手机号列表不能为空且格式必须为1[3-9]\\d{9}")
private List<String> phoneList;
}
六、进阶用法:分组校验(细化版,适配SpringBoot3.x+JDK17)
同一DTO可能适配"新增""更新""查询"等不同场景(如新增时ID为空、更新时ID非空、查询时ID可选),此时需用分组校验。核心要点:仅Spring扩展的@Validated支持分组,@Valid不支持;SpringBoot3.x+JDK17无额外配置,仅需遵循分组校验规范,以下细化全场景用法。
6.1 核心概念
分组校验本质是通过"分组接口"给校验注解分类,触发校验时指定分组,仅执行该分组下的校验规则。分组接口无需实现,仅作为标识,可继承扩展(实现分组复用)。
6.2 步骤1:定义分组接口(基础+继承扩展)
推荐按业务场景定义分组,支持继承(如查询分组继承公共分组),减少重复注解:
java
// 公共分组:所有场景都需校验的规则(如用户名、年龄)
public interface CommonGroup {}
// 新增分组:继承公共分组,新增专属规则
public interface AddGroup extends CommonGroup {}
// 更新分组:继承公共分组,更新专属规则
public interface UpdateGroup extends CommonGroup {}
// 查询分组:单独定义(仅校验必要参数,无需继承公共分组)
public interface QueryGroup {}
6.3 步骤2:实体类字段指定分组(多场景适配)
给每个字段的校验注解指定分组,可指定多个分组(如同时适配新增和更新),未指定分组的注解默认不执行(需注意避坑):
java
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Null;
import javax.validation.constraints.Size;
@Data
public class UserDTO {
// 新增:ID必须为空(AddGroup专属)
// 更新:ID必须≥1(UpdateGroup专属)
// 查询:ID可选(无需校验,不指定分组)
@Null(groups = AddGroup.class, message = "新增时ID必须为空")
@Min(value = 1, groups = UpdateGroup.class, message = "更新时ID必须≥1")
private Long id;
// 公共规则:所有继承CommonGroup的场景(新增、更新)都需校验
@NotBlank(groups = CommonGroup.class, message = "用户名不能为空")
@Size(min = 2, max = 20, groups = CommonGroup.class, message = "用户名长度2-20位")
private String username;
// 公共规则:新增、更新都需校验
@Min(value = 1, groups = CommonGroup.class, message = "年龄不能小于1")
private Integer age;
// 新增需校验,更新可选(仅AddGroup)
@NotBlank(groups = AddGroup.class, message = "新增时邮箱不能为空")
private String email;
// 查询专属:仅查询时校验手机号格式(QueryGroup)
@Pattern(regexp = "1[3-9]\\d{9}", groups = QueryGroup.class, message = "查询时手机号格式错误")
private String phone;
}
6.3 步骤3:Controller指定分组校验(单实体+List列表)
场景A:普通实体分组校验
用@Validated(分组接口.class)指定分组,触发对应规则,继承分组会自动执行父分组的校验规则:
java
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/user")
public class UserController {
// 新增用户:触发AddGroup(含CommonGroup公共规则)
@PostMapping("/add")
public Result<?> addUser(@RequestBody @Validated(AddGroup.class) UserDTO userDTO) {
return Result.success("用户新增成功");
}
// 更新用户:触发UpdateGroup(含CommonGroup公共规则)
@PutMapping("/update")
public Result<?> updateUser(@RequestBody @Validated(UpdateGroup.class) UserDTO userDTO) {
return Result.success("用户更新成功");
}
// 查询用户:触发QueryGroup(仅查询专属规则)
@GetMapping("/query")
public Result<?> queryUser(@Validated(QueryGroup.class) UserDTO userDTO) {
return Result.success("查询成功");
}
}
场景B:List列表的分组校验
列表分组校验与普通实体一致,仅需在Controller指定分组,List字段的@Valid会自动触发集合内元素的对应分组校验:
java
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
// 批量用户DTO(包含List<UserDTO>)
@Data
public class BatchUserDTO {
@NotBlank(message = "操作人ID不能为空")
private String operatorId;
// 核心:@Valid 触发List内每个UserDTO的分组校验
@Valid
@NotEmpty(message = "用户列表不能为空")
private List<UserDTO> userList;
}
// Controller 批量新增(触发AddGroup分组)
@PostMapping("/batch/add")
public Result<?> batchAddUser(@RequestBody @Validated(AddGroup.class) BatchUserDTO batchUserDTO) {
return Result.success("批量新增用户成功");
}
6.4 步骤4:分组校验避坑要点
-
未指定分组的校验注解,任何分组触发时都不会执行,需手动指定分组(或默认分组,下文说明)。
-
分组继承时,子分组会执行自身+父分组的所有校验规则,无需重复给字段加注解。
-
默认分组:若校验注解不指定groups,默认属于javax.validation.groups.Default分组,触发时需用@Validated(Default.class)。
-
多分组触发:可指定多个分组,如@Validated({AddGroup.class, QueryGroup.class}),会执行两个分组的所有规则。
七、进阶用法:嵌套对象校验
实体类中包含另一个实体类(非List),需在嵌套字段上添加@Valid注解,触发递归校验,与列表校验的核心逻辑一致,适配分组校验无差异。
java
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class OrderDTO {
@NotBlank(message = "订单号不能为空")
private String orderNo;
// 嵌套对象:收件人信息,@Valid触发校验(支持分组校验)
@Valid
@NotNull(message = "收件人信息不能为空")
private ReceiverDTO receiver;
}
// 嵌套的收件人DTO(可指定分组)
@Data
public class ReceiverDTO {
@NotBlank(groups = CommonGroup.class, message = "收件人姓名不能为空")
private String name;
@Pattern(regexp = "1[3-9]\\d{9}", groups = CommonGroup.class, message = "手机号格式错误")
private String phone;
@NotBlank(groups = AddGroup.class, message = "新增订单需填写收件地址")
private String address;
}
八、@Valid 与 @Validated 区别(关键避坑)
很多开发者混淆两者,记住核心区别,避免校验失效,适配SpringBoot3.x+JDK17无差异:
| 特性 | @Valid | @Validated |
|---|---|---|
| 来源 | JSR-380 标准注解 | Spring 扩展注解 |
| 分组校验 | 不支持 | 支持(核心优势) |
| 作用范围 | 字段、方法参数、嵌套对象 | 类、方法、参数 |
| 嵌套校验 | 支持(需显式加@Valid) | 需配合@Valid触发嵌套 |
九、Service层校验(完善版,适配SpringBoot3.x+JDK17)
Controller层校验仅针对接口入参,若Service层手动创建实体对象、接收非接口传入的参数(如定时任务、内部调用),需手动触发校验。SpringBoot3.x提供两种常用实现方式,均适配JDK17。
9.1 方式1:注入Validator接口(手动校验,推荐)
通过Validator接口手动校验实体,可指定分组,灵活适配各种业务场景,无需额外配置:
java
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.Set;
@Service
@Validated // 可选:开启Service层方法参数的自动校验
public class UserService {
// 注入Validator接口(SpringBoot自动配置,无需手动创建)
@Autowired
private Validator validator;
// 手动校验实体(无分组)
public void saveUser(UserDTO dto) {
// 手动校验,无分组时校验默认分组(Default.class)
Set<ConstraintViolation<UserDTO>> violations = validator.validate(dto);
handleValidationResult(violations);
// 业务逻辑
}
// 手动校验实体(指定分组,如新增分组)
public void addUser(UserDTO dto) {
// 第二个参数指定分组,可指定多个(如new Class[]{AddGroup.class})
Set<ConstraintViolation<UserDTO>> violations = validator.validate(dto, AddGroup.class);
handleValidationResult(violations);
// 业务逻辑
}
// 手动校验List列表(指定分组)
public void batchAddUser(List<UserDTO> userList) {
for (UserDTO dto : userList) {
Set<ConstraintViolation<UserDTO>> violations = validator.validate(dto, AddGroup.class);
handleValidationResult(violations);
}
// 业务逻辑
}
// 统一处理校验结果:抛出异常,交给全局异常处理器捕获
private void handleValidationResult(Set<ConstraintViolation<UserDTO>> violations) {
if (!violations.isEmpty()) {
// 拼接所有错误信息
String errorMsg = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(","));
// 抛出异常,可自定义异常,也可抛出IllegalArgumentException
throw new IllegalArgumentException("Service层参数校验失败:" + errorMsg);
}
}
}
9.2 方式2:@Validated + 方法参数注解(自动校验)
在Service类上添加@Validated,给方法参数加校验注解/指定分组,Spring会自动触发校验,失败抛出ConstraintViolationException,可被全局异常处理器捕获:
java
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Min;
import java.util.List;
@Service
@Validated // 开启Service层方法参数的自动校验
public class OrderService {
// 1. 单参数自动校验(指定分组)
public void checkOrderNo(@NotBlank(groups = QueryGroup.class) String orderNo) {
// 业务逻辑
}
// 2. 实体类自动校验(指定分组)
public void saveOrder(@Valid @Validated(AddGroup.class) OrderDTO orderDTO) {
// @Valid 触发嵌套校验(如ReceiverDTO),@Validated指定分组
// 业务逻辑
}
// 3. List列表自动校验(指定分组)
public void batchSaveOrder(@Valid @Validated(AddGroup.class) List<OrderDTO> orderList) {
// @Valid 触发列表内每个OrderDTO的校验,@Validated指定分组
// 业务逻辑
}
}
9.3 Service层校验避坑要点
-
方式2(@Validated自动校验)仅适用于"方法参数",若实体是Service内部创建(如new UserDTO()),无法触发自动校验,需用方式1手动校验。
-
手动校验时,若需校验嵌套对象/列表,无需额外操作,Validator接口会自动识别@Valid注解,触发递归校验。
-
全局异常处理器需捕获IllegalArgumentException(方式1手动抛出)和ConstraintViolationException(方式2自动抛出),已有处理器可直接适配。
十、常见避坑指南(必看,适配SpringBoot3.x+JDK17)
-
单参数校验失效:未在Controller/Service类上添加@Validated,仅在参数上加注解无效。
-
列表元素校验失效:忘记给List字段加@Valid,仅加@NotEmpty只能校验集合非空,无法校验内部元素。
-
嵌套校验失效:嵌套对象字段未加@Valid,无论用@Valid还是@Validated,都无法触发递归校验。
-
分组校验失效:未给校验注解指定分组,或触发时指定的分组与注解分组不匹配;未用@Validated(@Valid不支持分组)。
-
基础类型List校验错误:直接给List加@NotBlank,会导致校验失效,需用@Pattern或自定义注解。
-
SpringBoot3.x版本冲突:手动指定Hibernate Validator版本低于8.x,会导致适配JDK17失败,推荐使用Spring Boot父工程管理的版本。
-
Service层自动校验失效:实体是内部创建,未用手动校验;或未在Service类上添加@Validated。
十一、总结
本文适配JDK17+SpringBoot3.x环境,覆盖参数校验全场景,重点细化分组校验(含继承、多场景适配)、完善Service层双校验方式,核心是"注解驱动+全局异常处理",无需冗余的if-else判断,即可实现标准化校验。
核心流程:引入适配依赖→添加校验注解→指定分组(可选)→开启校验(@Valid/@Validated)→全局异常处理→Service层手动/自动校验兜底,搭配避坑要点,可适配绝大多数业务场景,提升代码简洁度与接口安全性。