Spring Validation 全面解析:从基础到高级实践
一、Spring Validation 是什么?
Spring Validation 是 Spring 框架提供的 数据校验工具,基于 JSR 380(Bean Validation 2.0)规范实现,用于对 Java Bean 的属性进行合法性校验。其核心目标是:
• 确保数据符合业务规则(如非空、格式正确)。
• 统一校验逻辑,避免重复代码。
• 与 Spring MVC 深度集成,支持自动触发校验和异常处理。
二、核心注解与使用场景
1. 内置注解
注解 | 作用 | 示例 |
---|---|---|
@NotBlank | 字符串非空且长度 > 0 | @NotBlank(message="用户名不能为空") |
@NotEmpty | 集合/数组/字符串非空 | @NotEmpty(message="列表不能为空") |
@Size | 字符串/集合长度范围 | @Size(min=6, max=20) |
@Pattern | 正则表达式匹配 | @Pattern(regexp="^[A-Za-z]+$") |
@Email | 邮箱格式校验 | @Email(message="邮箱格式错误") |
2. 自定义注解
当内置注解无法满足需求时,可自定义校验规则:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "手机号格式错误";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
3. 校验器实现
public class PhoneValidator implements ConstraintValidator<Phone, String> {
private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return StringUtils.isEmpty(value) || value.matches(PHONE_REGEX);
}
}
三、Bean Validation 规范(JSR 380)与 Hibernate Validator
1. Bean Validation 规范(JSR 380)
定义:
Bean Validation 是 Java EE 的一项标准规范(JSR 380),旨在为 Java Bean 提供统一的校验机制。它定义了一套注解(如 @NotNull
、@Email
)和接口(如 ConstraintValidator
),用于描述数据校验规则。
核心目标:
• 解耦校验逻辑:将校验规则与业务代码分离。
• 标准化校验注解:通过统一注解(如 @Size
、@Pattern
)实现跨框架兼容性。
• 支持嵌套对象校验:通过级联校验(如 @Valid
)验证复杂对象结构。
关键接口:
• ConstraintValidator
:自定义校验逻辑的核心接口。
• ConstraintViolation
:表示校验失败的具体信息(如字段名、错误消息)。
与 Spring Validation 的关系:
Spring Validation 是对 Bean Validation 规范的实现封装,通过 @Valid
和 @Validated
注解触发校验,并集成到 Spring MVC 的异常处理流程中。
2. Hibernate Validator
定义:
Hibernate Validator 是 Bean Validation 规范的 参考实现,由 Red Hat 开发和维护。它是 Spring Boot 默认使用的校验引擎。
核心功能:
• 实现 JSR 380 定义的所有注解(如 @Email
、@Size
)。
• 支持自定义校验注解和校验器。
• 提供国际化错误消息支持(通过 ValidationMessages.properties
)。
在 Spring Boot 中的作用:
• 解析 @NotBlank
、@Email
等注解的校验逻辑。
• 生成 ConstraintViolationException
异常,供 Spring 捕获并处理。
依赖关系:
Spring Boot 的 spring-boot-starter-validation
会自动引入 Hibernate Validator:
<!-- Maven 依赖树示例 -->
org.springframework.boot:spring-boot-starter-validation
\- org.hibernate.validator:hibernate-validator:6.2.5.Final
3. Bean Validation 与 Hibernate Validator 的协作流程
1. 定义校验规则:
在 DTO 类字段上使用 JSR 380 注解(如 @Email
)。
public class UserForm {
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式错误")
private String email;
}
2. 触发校验:
在 Controller 方法参数前添加 @Valid
,Spring 会委托 Hibernate Validator 执行校验。
@PostMapping("/register")
public ResponseEntity<?> registerUser(@Valid @RequestBody UserForm form) {
// 校验通过后执行逻辑
}
3. 处理异常:
校验失败时,Hibernate Validator 抛出 ConstraintViolationException
,Spring 将其包装为 MethodArgumentNotValidException
,最终由全局异常处理器捕获。
四、Spring Validation 的工作原理
1. 校验触发方式
• 字段级校验:在 DTO 类的字段上添加注解,通过 @Valid
触发。
public class UserForm {
@NotBlank
@Email
private String email;
}
@PostMapping("/register")
public ResponseEntity<?> registerUser(@Valid @RequestBody UserForm form) {
// 校验通过后执行逻辑
}
• 方法参数校验:在 Controller 方法参数上使用 @Validated
+ 注解。
@RestController
@Validated
public class UserController {
@PostMapping("/sendEmail")
public ResponseEntity<?> sendEmail(
@RequestParam("email")
@Email(message = "邮箱格式错误")
String email
) {
// 业务逻辑
}
}
2. 核心区别
场景 | @Valid (字段校验) | @Validated (参数校验) |
---|---|---|
触发对象 | DTO 对象的字段 | Controller 方法的参数 |
异常类型 | MethodArgumentNotValidException | ConstraintViolationException |
适用场景 | 复杂对象校验(如表单提交) | 简单参数校验(如单个字符串) |
五、全局异常处理
1. 捕获不同异常
• 字段校验失败(@Valid
触发):
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationException(MethodArgumentNotValidException ex) {
List<ApiError> errors = ex.getBindingResult().getFieldErrors().stream()
.map(error -> new ApiError(error.getField(), error.getDefaultMessage()))
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(errors);
}
• 参数校验失败(@Validated
触发):
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<?> handleConstraintViolation(ConstraintViolationException ex) {
List<ApiError> errors = ex.getConstraintViolations().stream()
.map(violation -> new ApiError(violation.getPropertyPath().toString(), violation.getMessage()))
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(errors);
}
2. 统一响应格式
@Data
@AllArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data);
}
public static ApiResponse<?> fail(String message) {
return new ApiResponse<>(400, message, null);
}
}
六、高级用法
1. 跨字段校验
通过 @ScriptAssert
或自定义注解实现字段间逻辑校验:
@ScriptAssert(lang = "javascript", script = "_this.password != _this.confirmPassword")
public class UserForm {
private String password;
private String confirmPassword;
}
2. 国际化支持
在 ValidationMessages.properties
中定义错误消息:
NotBlank.userForm.email=邮箱不能为空
Email.userForm.email=邮箱格式错误
3. 嵌套对象校验
使用 @Valid
触发嵌套对象的级联校验:
public class OrderForm {
@Valid
@NotNull
private UserForm user;
}
七、常见问题与最佳实践
1. 常见误区
• 依赖缺失:忘记添加 spring-boot-starter-validation
。
• 误用注解:在 Controller 方法参数上直接使用 @Email
但未加 @Validated
。
• 忽略空值处理:未对可能为 null
的字段添加 @NotNull
。
2. 最佳实践
• 优先使用内置注解:如无特殊需求,避免过度自定义。
• 统一异常处理:通过 @RestControllerAdvice
集中管理错误响应。
• 校验粒度控制:简单参数用 @Validated
,复杂对象用 @Valid
。
八、总结
概念 | **说明 |
---|---|
Bean Validation (JSR 380) | Java EE 标准规范,定义校验注解和接口。 |
Hibernate Validator | Bean Validation 的参考实现,Spring Boot 默认使用。 |
Spring Validation | 对 Bean Validation 的封装,提供 @Valid 和 @Validated 注解。 |
核心结论:
Spring Validation 的底层依赖 Hibernate Validator 实现校验逻辑,开发者通过 JSR 380 注解声明规则,最终由 Spring 集成到 MVC 异常处理流程中。理解这一分层架构,是掌握 Spring Validation 的关键。