1.SpringBoot-Validate是什么
它简化了 Java Bean Validation 的集成。Java Bean Validation 通过 JSR 380,也称为 Bean Validation 2.0,是一种标准化的方式,用于在 Java 应用程序中对对象的约束进行声明式验证。它允许开发人员使用注解来定义验证规则,并自动将规则应用于相应的字段或方法参数
2.引入依赖
springboot 版本是 2.7.18
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
3.参数验证
3.1 实体上加@Validated注解
@Data
public class Person {
@NotBlank(message = "name 姓名不能为空")
private String name;
@NotNull(message = "age 年龄不能为空")
@Min(value = 0, message = "年龄不能小于0")
private Integer age;
@NotNull(message = "gender 性别不能为空")
private Integer gender;
@Email(regexp = RegularConstant.EMAIL, message = "email 邮箱格式不正确")
private String email;
@Pattern(regexp = RegularConstant.PHONE, message = "phone 手机号格式不正确")
private String phone;
@Past(message = "birthday 生日日期有误")
private LocalDate birthday;
}
3.2 开启校验规则
import com.zhouzz.validate.entity.Person;
import com.zhouzz.validate.model.*;
import org.springframework.validation.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import java.util.*;
@RestController
public class TestController {
@PostMapping("/test1")
public Result save(@Validated @RequestBody Person person, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<FieldError> fieldErrorList = bindingResult.getFieldErrors();
Map<String, String> map = new HashMap<>(fieldErrorList.size());
fieldErrorList.forEach(item -> {
// 如果没有在相应的注解中加message,则会获取默认信息
// 例如:@NotBlank(message = "品牌名必须提交"),获取的message则为品牌名必须提交
String message = item.getDefaultMessage();
// 获取哪个字段出现的问题
String field = item.getField();
map.put(field, message);
});
return Result.fail(BusinessErrorEnum.PARAM_VALIDATE_FAILED).putData(map);
}
// 执行正常逻辑
System.out.println(person);
return Result.success();
}
/**
* 单个接口处理
* @param person
* @return
*/
@PostMapping("/test2")
public Result test2(@Validated @RequestBody Person person, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return Result.fail(BusinessErrorEnum.PARAM_VALIDATE_FAILED);
}
return Result.success(person);
}
/**
* 全局处理(需要结合全局异常捕获处理)
* @param person
* @return
*/
@PostMapping("/test3")
public Result test3(@Validated @RequestBody Person person) {
return Result.success(person);
}
}
3.3 测试一下
1.方式一: 手写参数校验捕获
POST http://localhost:8080/test1
Content-Type: application/json
Accept: application/json
{
}
返回结果:
{
"code": 600,
"msg": "参数检验失败",
"data": {
"name": "name 姓名不能为空",
"gender": "gender 性别不能为空",
"age": "age 年龄不能为空"
}
}
2.方式二:直接校验
POST http://localhost:8080/test2
Content-Type: application/json
Accept: application/json
{
}
返回结果:
{
"code": 600,
"msg": "参数检验失败",
"data": null
}
很明显第二种提示信息太少了, 但是每个接口向方式一那样手动一个个处理,就显得很麻烦了。
方式三:定义一个全局处理处理参数校验
import com.zhouzz.validate.model.BusinessErrorEnum;
import com.zhouzz.validate.model.Result;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
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.List;
import java.util.stream.Collectors;
/**
* 全局异常捕获类
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理 form data方式调用接口校验失败抛出的异常 (对象参数)
*/
@ExceptionHandler(BindException.class)
public Result error(BindException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> errorMessages = fieldErrors.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return Result.fail(BusinessErrorEnum.PARAM_VALIDATE_FAILED.getCode(), errorMessages.toString());
}
/**
* 处理 json 请求体调用接口校验失败抛出的异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result error(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> errorMessages = fieldErrors.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return Result.fail(BusinessErrorEnum.PARAM_VALIDATE_FAILED.getCode(), errorMessages.toString());
}
/**
* 单个参数校验失败抛出的异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result error(ConstraintViolationException e) {
String errorMsg = e.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessageTemplate)
.collect(Collectors.joining());
return Result.fail(errorMsg);
}
}
测试下
POST http://localhost:8080/test3
Content-Type: application/json
Accept: application/json
{
}
返回结果:
{
"code": 600,
"msg": "[name 姓名不能为空, age 年龄不能为空, gender 性别不能为空]",
"data": null
}
4.相关注解信息
注解 | 数据类型 | 说明 |
---|---|---|
@NotBlank | CharSequence | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
@NotEmpty | CharSequence,Collection,Map,Arrays | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@Length(min=下限, max=上限) | CharSequence | 验证注解的元素值长度在min和max区间内 |
@NotNull | 所有类型 | 验证注解的元素值不是null |
@Null | 所有类型 | 验证注解的元素值是null |
@Max(value=n) | Number的任何子类型 | 验证注解的元素值小于等于@Max指定的value值 |
@Min(value=n) | Number的任何子类型 | 验证注解的元素值大于等于@Min指定的value值 |
@Size(min=最小值, max=最大值) | 字符串,集合,映射和数组 | 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小 |
CharSequence | 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 | |
@Pattern(regex=正则表达式, flag=) | CharSequence | 验证注解的元素值与指定的正则表达式匹配 |
@Range(min=最小值, max=最大值 | CharSequence, Collection, Map and Arrays, BigDecimal, BigInteger, CharSequece, byte, short, int, long以及原始类型各自的包装 | 验证注解的元素值在最小值和最大值之间 |
@AssertFalse | Boolean, boolean | 验证注解的元素值是false |
@AssertTrue | Boolean, boolean | 验证注解的元素值是true |
@DecimalMax(value=n) | Number和CharSequence的任何子类型 | 验证注解的元素值小于等于@ DecimalMax指定的value值 |
@DecimalMin(value=n) | Number和CharSequence的任何子类型 | 验证注解的元素值小于等于@ DecimalMin指定的value值 |
@Digits(integer=整数位数, fraction=小数位数) | Number和CharSequence的任何子类型 | 验证注解的元素值的整数位数和小数位数上限 |
@Future | 日期类型 | 验证注解的元素值(日期类型)比当前时间晚 |
@Past | 日期类型 | 验证注解的元素值(日期类型)比当前时间早 |
@Valid | Any non-primitive type(引用类型) | 验证关联的对象,如账户对象里有一个订单对象,指定验证订单对象 |
5.分组校验
当我们新增和修改字段时,有可能校验规则是不一样的,那么该如何处理呢?
5.1 定义分组信息(唯一即可)
public interface AddGroup {
}
public interface UpdateGroup extends Default {
}
5.2 实体类上指定groups
在分组参数后指定它的groups,需要指定在什么情况下需要进行校验,类型是一个接口数组
@Data
public class Person {
@NotBlank(message = "name 姓名不能为空", groups = AddGroup.class)
private String name;
@NotNull(message = "age 年龄不能为空", groups = UpdateGroup.class)
@Min(value = 0, message = "年龄不能小于0")
private Integer age;
@NotNull(message = "gender 性别不能为空", groups = {AddGroup.class, UpdateGroup.class})
private Integer gender;
@NotBlank(message = "email 邮箱不能为空", groups = Default.class)
@Email(regexp = RegularConstant.EMAIL, message = "email 邮箱格式不正确", groups = Default.class)
private String email;
@NotBlank(message = "phone 手机号不能为空", groups = UpdateGroup.class)
@Pattern(regexp = RegularConstant.PHONE, message = "phone 手机号格式不正确")
private String phone;
@Past(message = "birthday 生日日期有误")
private LocalDate birthday;
}
5.3 控制器指定分组
然后再我们的controller层参数前加上@Validated(value = {UpdateGroup.class})注解,指定它是哪一组
import com.zhouzz.validate.entity.Person;
import com.zhouzz.validate.group.AddGroup;
import com.zhouzz.validate.group.UpdateGroup;
import com.zhouzz.validate.model.Result;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.groups.Default;
@RestController
public class TestGroupController {
/**
* 测试添加分组
* @param person
* @return
*/
@PostMapping("/testAddGroup")
public Result testAddGroup(@Validated(AddGroup.class) Person person) {
return Result.success(person);
}
/**
* 测试修改分组
* @param person
* @return
*/
@PostMapping("/testUpdateGroup")
public Result testUpdateGroup(@Validated(UpdateGroup.class) Person person) {
return Result.success(person);
}
/**
* 测试添加和修改分组
* @param person
* @return
*/
@PostMapping("/testAddAndUpdateGroup")
public Result testAddAndUpdateGroup(@Validated({UpdateGroup.class, AddGroup.class}) Person person) {
return Result.success(person);
}
/**
* 测试默认分组
* @param person
* @return
*/
@PostMapping("/testDefaultGroup")
public Result testDefaultGroup(@Validated(Default.class) Person person) {
return Result.success(person);
}
}
1.测试AddGroup
POST http://localhost:8080/testAddGroup
Content-Type: application/json
Accept: application/json
{
}
返回结果:
{
"code": 600,
"msg": "[name 姓名不能为空, gender 性别不能为空]",
"data": null
}
2.测试testUpdateGroup
POST http://localhost:8080/testUpdateGroup
Content-Type: application/json
Accept: application/json
{
}
返回结果:
{
"code": 600,
"msg": "[gender 性别不能为空, phone 手机号不能为空, age 年龄不能为空, email 邮箱不能为空]",
"data": null
}
6.自定义校验规则
例如:现在我有一个字段gender它的取值就三种0:保密 1:男 2:女,像这种有限个数的枚举值我们该如何去限制呢?这就要使用到的自定义校验注解了
6.1 自定义校验注解
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue {
// 默认错误消息
String message() default ENUM_VALUE_MESSAGE;
String[] strValues() default {};
int[] intValues() default {};
// 分组
Class<?>[] groups() default {};
// 负载,可以增加自定义校验逻辑
Class<? extends Payload>[] payload() default {};
// 指定多个时使用
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {
EnumValue[] value();
}
}
public interface MessageConstant {
String ENUM_VALUE_MESSAGE = "性别字段值只能为0、1、2";
}
6.2 自定义校验器
/**
* 枚举值校验注解处理类
*/
public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {
/**
* 字符串
*/
private final Set<String> strValueSet = new HashSet<>();
/**
* 数值
*/
private final Set<Integer> intValueSet = new HashSet<>();
@Override
public void initialize(EnumValue constraintAnnotation) {
strValueSet.addAll(Arrays.asList(constraintAnnotation.strValues()));
for (int i : constraintAnnotation.intValues()) {
intValueSet.add(i);
}
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value instanceof Integer) {
// 整数值类型
return intValueSet.contains(value);
} else if (value instanceof String) {
// 字符串类型
return strValueSet.contains(value);
}
return false;
}
}
6.3 关联自定义的校验器和自定义的校验注解
@EnumValue(intValues = {0, 1, 2}, groups = AddGroup.class)
@NotNull(message = "gender 性别不能为空", groups = {AddGroup.class, UpdateGroup.class})
private Integer gender;
评论区