SpringBoot Validation

Validation

使用springboot的validation进行参数校验。

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

注解

在validation中定义了一系列约束(constraint)注解。

注解功能
@NotNull不能为Null,可以为空
@Null必须为null
@NotBlank字符串不能为null,trim()后不能为"",至少有一个非空白字符
@NotEmpty不能为null,集合,数组,mao等size不能为0,字符串可以是空白字符
@Max(value)注解元素须是数值类型,不支持float,double,最大不超过该值
@Min(value)注解元素须是数值类型,不支持float,double,最小不小于该值
@Positive判断数字是否为正数
@PositiveOrZero判断正数或零
@Negative判断数字是否为负数
@NegativeOrZero判断负数或零
@Digits(integer,fraction)必须为数字,且整数位数和小数位数必须在指定范围
@Size(min,max)字符串长度,或集合,数组,map等size必须在指定范围内,
@Email字符串必须是email格式
@Length(min,max)注解对象只能是字符串,字符串长度必须在指定范围
@AssertFalse可以为null,不为null必须为false
@AssertTrue可以为null,不为null必须为true
@Past注解元素须为日期时间,判断是否是过去的日期
@PastOrPresent判断日期是否是过去或现在
@Future注解元素须为日期时间,判断是否是未来的日期
@FutureOrPresent判断日期是否是未来或现在
@Pattern(value)判断是否符合正则表达式

@Valid和@Validated

@Valid可以添加在方法参数,方法返回,成员变量,普通方法,构造方法上,表示需要进行约束校验。

@Validated可以添加在类,方法参数,普通方法上,进行约束校验,支持分组校验

一般来说,这两个都放在要加校验的方法参数前。

两者容易搞混。

它们的区别有:

  • @Valid可以递归校验,即如果A类中有一个B类变量,而B类中也有约束注解,那么在检查A类约束时,会自动检查B类中的成员变量,而@Validated不会检查
  • @Validated可以分组校验,而@Valid不行

一般来说,在控制器方法参数前使用@Validated,在实体类的复杂对象前,使用@Valid

例如,实体类User中有一个Address变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
public class User {

@NotBlank(message = "用户名不能为空")
public String userName;

@Min(value = 20,message = "年龄须不小于20岁")
@Max(value = 80,message = "年龄须不大于80岁")
private Integer age;

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

@Valid
private Address address;

}
1
2
3
4
5
6
7
8
9
@Data
public class Address {

@Size(min = 3,max = 12, message = "字符串长度应该3~12之间")
private String country;

@Length(min = 5,max = 15, message = "字符串长度应该5~15之间")
private String province;
}

在控制器方法中可以这样写:

1
2
3
4
@PostMapping("/")
public User test(@Validated @RequestBody User user) {
return user;
}

就会递归校验User和Address的成员变量约束注解。

异常处理

校验参数后,会将校验结果发到一个BindingResult类中,如果出现错误,会抛出异常,所以需要手动处理异常。

可以这样处理,在控制器方法中处理:

1
2
3
4
5
6
7
8
9
10
@PostMapping(value="/")
public void test(@Validated @RequestBody User user, BindingResult result) {
if(bindingResult.hasErrors()) {
List<ObjectError> errorList = result.getAllErrors();
for(ObjectError e : errorList){
System.out.println(e.getDefaultMessage());
}
}

}

如果使用这种处理方法,需要在每个控制器方法中都这样写,冗余很高,因此一般不使用这种方法。

全局异常处理

往往是创建一个全局异常类来集中处理所有异常。

使用@ControllerAdvice@RestControllerAdvicce来注解全局异常处理类。

使用@ExceptionHandler(class)放在方法上,来指定要处理的异常类。

1
2
3
4
5
6
7
8
9
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(BindException.class)
public String methodNotValid(BindException ex) {
return "异常类为:" + ex.getClass().getName() + ", 错误信息为:" + ex.getBindingResult().getFieldError().getDefaultMessage();

}
}

在使用@Validated@Valid注解校验参数出错后,会抛出MethodArgumentNotValidException异常,它是BindException的子类。

可以直接处理MethodArgumentNotValidException,也可以处理BindException。一般是将错误信息以统一的json的格式返回给前端。

## 分组校验

很多时候,在校验参数时,有不同的情况。例如在新增用户时,用户名不能为空,但是在修改用户信息时,用户名可以为空。

因此,分组校验就是针对相同的参数或对象,在不同情况下使用不同的校验规则。

首先定义两个分组接口Create和Update,继承Default接口。

继承Default不是必须的,如果继承了Default,那么@Validated(value=Create.class)的校验范畴就是Create和Default;如果没有继承Default,那么@Validated(value=Create.class)的校验范畴只有Create,只有手动指定Default:@Validated(value={Create.class,Default.class}),校验范畴才是Create和Default

1
2
public interface Create extends Default {
}
1
2
public interface Update extends Default {
}

在实体类中,给参数加校验注解时,指定所属分组。

1
2
3
4
5
6
7
8
9
10
11
12
13
public Class Student {
@NotEmpty(groups = Create.class)
private String name;

@NotEmpty(groups = Update.class)
private String motto;

@NotEmpty(groups = {Create.class,Update.class})
private String hobby;

@NotEmpty(groups = Default.class)
private String address;
}

当没有指定分组时,默认是Default分组,因此成员变量address的groups可以不写。

然后在控制器方法中启动校验时,使用@Validatedvalue属性指定校验分组。

1
2
3
4
@PostMapping("/")
public String test(@Validated(value=Create.class) Student student){

}

在上述这种情况下,隶属于Create和Default组的校验设置,都会触发校验。

其他情况同理。

自定义约束

开发自定义约束需要两步:

  1. 编写自定义约束的注解
  2. 编写自定义约束的校验器ConstraintValidator

以开发一个枚举校验为例。

首先创建自定义约束注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumConstraintValidator.class)
public @interface Enum {
/**
* 给定值的列表,验证参数是否在列表内
* @return
*/
String[] value();

String message() default "传入非法参数";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}

其中@Constraint(validatedBy = EnumConstraintValidator.class)指定了使用EnumConstraintValidator类校验该注解。

value是自定义的成员变量,其他三个是所有约束注解都要有的成员变量。

message是检验出错的默认输出信息。

groups是分组信息,payload不用管。

然后创建EnumConstraintValidator类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class EnumConstraintValidator implements ConstraintValidator<Enum,String> {
private String[] verification;

@Override
public void initialize(Enum anEnum) {
verification = anEnum.value();
}

@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if(verification == null){
return true;
}
for(String c : verification){
if(value.equals(c)){
return true;
}
}
return false;
}
}

继承了ConstraintValidator<A,T>接口,A表示自定义约束注解,T表示参数值的类型。有两个方法。

在初始化时,可传入注解中的value变量数组,然后在isValid方法中,将实际输入与value数组中的值一一比较,如果匹配成功,返回true,否则返回false。


SpringBoot Validation
https://zhaoquaner.github.io/2022/11/23/SpringBoot/13-SpringBoot-Validation/
更新于
2022年11月27日
许可协议