JSR 303 数据校验框架及实践

一、JSR 303 定义

Java 为 Bean 数据合法性校验提供的标准框架,包含于 JavaEE 6.0 标准中。

  • 核心机制:通过在 Bean 属性上标注 @NotNull@Max 等标准注解定义校验规则,通过标准接口完成校验。

二、数据校验使用流程

  1. 引入依赖

    spring-boot-starter-validation(整合 JSR 303 实现,如 Hibernate Validator)。

  2. 定义数据封装 Bean(VO)

    通常为接收前端参数的实体类(如 EmployeeAddVo)。

  3. 标注校验注解

    在 Bean 字段上添加标准注解(如 @NotBlank@Email)或自定义注解,指定错误提示消息。

  4. 开启校验

    在 Controller 方法参数上使用 @Valid 或 @Validated 触发校验。

  5. 处理校验结果

    • 方式 1:通过 BindingResult 手动获取校验错误(需紧跟被校验参数)。
    • 方式 2:结合全局异常处理,统一捕获校验失败异常(推荐)。
  6. 自定义校验规则

    通过 “自定义注解 + 校验器” 实现复杂校验(如性别只能为 “男” 或 “女”)。

  7. 错误消息国际化

    校验注解的 message 属性使用占位符(如 {gender.message}),结合 i18n 配置文件实现多语言提示。

  8. 全局异常处理

    捕获 MethodArgumentNotValidException 异常,统一封装并返回校验错误信息。

三、自定义校验实现(以性别校验为例)

1. 定义自定义注解 @Gender

java

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.atguigu.practice.annotation;

import com.atguigu.practice.vaildator.GenderValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = {GenderValidator.class}) // 关联具体校验器
@Target({ElementType.FIELD}) // 作用于字段
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
public @interface Gender {
String message() default "{jakarta.validation.constraints.NotNull.message}"; // 错误提示(支持国际化)
Class<?>[] groups() default {}; // 分组校验(默认无分组)
Class<? extends Payload>[] payload() default {}; // 附加信息(如严重级别)
}
2. 实现校验器 GenderValidator

需实现 ConstraintValidator<注解类型, 校验字段类型> 接口:

java

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.atguigu.practice.vaildator;

import com.atguigu.practice.annotation.Gender;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class GenderValidator implements ConstraintValidator<Gender, String> {
/**
* 校验逻辑:判断值是否为"男"或"女"
* @param value 前端传入的待校验值
* @param context 校验上下文(可修改提示信息等)
* @return 校验通过返回true,否则false
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return "男".equals(value) || "女".equals(value);
}
}
3. 在 VO 中使用自定义注解

java

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.atguigu.practice.vo.req;

import com.atguigu.practice.annotation.Gender;
import jakarta.validation.constraints.*;
import lombok.Data;
import java.math.BigDecimal;

@Data
public class EmployeeAddVo {
@NotBlank(message = "姓名不能为空") // 非空校验(字符串不能为null或空串)
private String name;

@NotNull(message = "年龄不能为空") // 非null校验
@Max(value = 150, message = "年龄不能大于150岁") // 最大值限制
@Min(value = 0, message = "年龄不能小于0岁") // 最小值限制
private Integer age;

@Email(message = "邮箱格式不正确") // 邮箱格式校验
private String email;

@Gender(message = "{gender.message}") // 使用自定义注解,message关联国际化配置
private String gender;

private String address;
private BigDecimal salary;
}
4. Controller 中开启校验

java

运行

1
2
3
4
5
6
7
8
9
10
@PostMapping("/employee")
// @Valid 触发对 EmployeeAddVo 的校验,校验失败会抛出异常
public R add(@RequestBody @Valid EmployeeAddVo vo) {
// VO 转 DO(属性拷贝)
Employee employee = new Employee();
BeanUtils.copyProperties(vo, employee);

employeeService.addEmp(employee);
return R.ok();
}
5. 全局异常处理校验错误

java

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
@ExceptionHandler(MethodArgumentNotValidException.class)
public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
BindingResult result = e.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();

// 封装字段与错误信息的映射
Map<String, String> errorsMap = new HashMap<>();
for (FieldError error : fieldErrors) {
errorsMap.put(error.getField(), error.getDefaultMessage());
}

return R.error(500, "参数不正确", errorsMap);
}

核心说明

  • 标准注解(如 @NotBlank@Email)适用于通用校验,自定义注解适用于业务特定规则。
  • 全局异常处理替代手动处理 BindingResult,简化代码并统一响应格式。
  • 国际化通过 message 占位符 + i18n 文件实现,支持多语言场景。