如何优雅地进行参数校验(基于 Jakarta Bean Validation 与 Hibernate Validator)
一、前言
在实际的服务端开发中,参数校验是保障系统健壮性和用户体验的关键一环。很多系统不注重这块,导致请求穿透到业务层或数据库层才抛出异常,不仅浪费系统资源,还增加了调试难度。
本篇文章将以 Jakarta Bean Validation(JSR 380 标准)为核心,结合 Hibernate Validator 和 Spring Boot 的使用方式,讲解如何构建一个优雅、清晰、可维护的参数校验体系。
二、Bean Validation 简介
Jakarta Bean Validation 是 Java 官方定义的一套对象校验规范(前称为 JSR 303/380)。Hibernate Validator 是其参考实现。
在 Spring Boot 中,只需要添加如下依赖即可使用(通常 Spring Boot Starter 已默认引入):
1 2 3 4
| <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency>
|
Spring Boot 3 之后,大部分依赖已切换到 jakarta.*
包名,因此我们在代码中使用:
1 2
| import jakarta.validation.Valid; import jakarta.validation.constraints.*;
|
三、常用校验注解
注解 |
说明 |
@NotNull |
不能为 null |
@NotBlank |
不能为 null 且字符串不为空 |
@NotEmpty |
集合/数组/Map 不能为空 |
@Size |
字符串、集合长度限制 |
@Min / @Max |
数字大小限制 |
@Pattern |
正则表达式校验字符串格式 |
@Email |
邮箱地址格式校验 |
四、Controller 入参校验
4.1 基本使用示例
1 2 3 4 5 6 7 8 9
| @RestController @RequestMapping("/user") public class UserController { @PostMapping("/create") public ResponseEntity<?> createUser(@RequestBody @Valid UserDTO userDTO) { return ResponseEntity.ok("创建成功"); } }
|
1 2 3 4 5 6 7 8 9
| @Data public class UserDTO { @NotBlank(message = "用户名不能为空") private String username; @Min(value = 18, message = "年龄不能小于18岁") private Integer age; }
|
4.2 统一异常处理
1 2 3 4 5 6 7 8
| @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<?> handleValidException(MethodArgumentNotValidException ex) { ...自定义处理 } }
|
五、分组校验(Groups)
不同接口对参数的要求可能不同,例如:创建时必须填写 ID,更新时不能改用户名。此时可以使用分组:
1 2 3 4 5 6 7 8 9 10 11
| public class UserDTO {
public interface Create {} public interface Update {}
@NotNull(groups = Update.class, message = "ID 不能为空") private Long id; @NotBlank(groups = Create.class, message = "用户名必填") private String username; }
|
Controller 中使用:
1 2
| @PostMapping("/create") public ResponseEntity<?> create(@RequestBody @Validated(UserDTO.Create.class) UserDTO user) {...}
|
六、嵌套对象校验
1 2 3 4 5 6 7 8 9 10
| @Data public class OrderDTO {
@NotBlank private String orderId; @Valid @NotNull private UserDTO user; }
|
@Valid
不能省略,否则嵌套对象校验不会生效。
七、自定义校验器
7.1 场景:用户名不能包含特殊字符
定义注解
1 2 3 4 5 6 7 8
| @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UsernameValidator.class) public @interface ValidUsername { String message() default "用户名格式非法"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
|
实现校验器
1 2 3 4 5 6 7 8 9
| public class UsernameValidator implements ConstraintValidator<ValidUsername, String> {
private static final Pattern PATTERN = Pattern.compile("^[a-zA-Z0-9_]+$"); @Override public boolean isValid(String value, ConstraintValidatorContext context) { return value != null && PATTERN.matcher(value).matches(); } }
|
八、最佳实践总结
- 接口参数使用 DTO 封装,避免直接用实体类
- 使用
jakarta.validation.Valid
或 @Validated
搭配分组控制
- 尽可能将错误集中到统一异常处理器中返回
- 错误信息支持国际化(i18n)
- 结合 Lombok 使用时注意构造器默认值问题
九、常见坑点
问题 |
原因/解决方式 |
嵌套对象校验无效 |
缺少 @Valid 注解 |
分组校验不生效 |
使用了 @Valid 而非 @Validated |
单个字段校验失败但没抛出异常 |
参数未加 @RequestBody 或未被扫描到校验注解 |