如何优雅地进行参数校验(基于 Jakarta Bean Validation 与 Hibernate Validator)

如何优雅地进行参数校验(基于 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 或未被扫描到校验注解

如何优雅地进行参数校验(基于 Jakarta Bean Validation 与 Hibernate Validator)
http://example.com/2024/03/12/java/springboot/parameter-validation-jakarta/
作者
Donghao Ji
发布于
2024年3月12日
许可协议