参数校验

在日常开发中,经常需要对前端传过来的数据进行,非空等有效校验。但是如果使用if (username == null)的方式进行手动判断,会十分麻烦并且每次都需要校验;

于是Validator框架应运而生,提供了很多方便的校验注解(非空、邮箱是否有效、长度限制等)

详细使用

一般SpringBoot的项目会使用Spring Validation,它是对Hibernate Validation的二次封装。在SpringMVC模块中添加了自动校验。并将校验信息封装到特定的类中。

提供的校验注解:

  • @Null 被注释的元素必须为null
  • @NotNull 被注释的元素必须不为null
  • @AssertTrue 被注释的元素必须为true
  • @AssertFalse 被注释的元素必须为false
  • @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max, min) 被注释的元素的大小必须在指定的范围内
  • @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
  • @Past 被注释的元素必须是一个过去的日期
  • @Future 被注释的元素必须是一个将来的日期
  • @Pattern(value) 被注释的元素必须符合指定的正则表达式
  • @NotBlank(message =) 验证字符串非 null,且长度必须大于 0
  • @Email 被注释的元素必须是电子邮箱地址
  • @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
  • @NotEmpty 被注释的字符串的必须非空
  • @Range(min=,max=,message=) 被注释的元素必须在合适的范围内

基本使用

首先导入依赖

1
2
3
4
5
<!-- SpringBoot有专门的启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

给需要校验的实体类添加校验注解

TestDemo:

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
27
28
29
30
31
import lombok.Data;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Data
public class TestDemo {
@NotNull(message = "id必须非空")
private Long id;

@NotBlank(message = "用户名不能为空,并且长度必须大于0")
@Size(min = 6, max = 11, message = "用户名长度必须是6-11个字符")
private String username;

@NotBlank(message = "用户密码不能为空,并且长度必须大于0")
@Size(min = 6, max = 16, message = "密码长度必须是6-16个字符")
private String password;

@Email(message = "邮箱格式错误")
@NotBlank(message = "邮箱不能为空,并且长度必须大于0")
private String email;

/**
* 嵌套校验需要加@Valid
*/
@Valid
@NotNull
private Test02 test02;

}

Test02:

1
2
3
4
5
6
7
8
9
10
import lombok.Data;

import javax.validation.constraints.NotNull;

@Data
public class Test02 {

@NotNull(message = "id必须非空")
private Long id;
}

测试校验

1
2
3
4
5
6
7
8
9
10
//注意需要给要校验的对象添加@Validated或者@valid
@PostMapping("/insert")
public String insertTestUser(@RequestBody @Validated TestDemo testUser, BindingResult bindingResult){
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
for (ObjectError error : bindingResult.getAllErrors()) {
return error.getDefaultMessage();
}
log.info("用户名{}的邮箱为{}", testUser.getUsername(),testUser.getEmail());
return "success";
}

@Valid注解与@Validated注解功能差不多

不同点在于:

  • @Valid属于javax包下,而@Validated属于Spring下
  • @Valid支持嵌套校验、而@Validated不支持
  • @Validated支持分组,而@Valid不支持

自定义校验注解

定义自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = PhoneValidator.class
)
public @interface Phone {
String message() default "手机格式不正确!";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

}

定义该注解的校验器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PhoneValidator implements ConstraintValidator<Phone, String> {
@Override
public boolean isValid(String phoneNum, ConstraintValidatorContext constraintValidatorContext) {
// 1: 如果用户没输入直接返回不校验,因为空的判断交给@NotNull去做就行了
if (phoneNum == null && phoneNum.length() == 0) {
return true;
}
Pattern p = Pattern.compile("^(13[0-9]|14[5|7|9]|15[0|1|2|3|5|6|7|8|9]|17[0|1|6|7|8]|18[0-9])\\d{8}$");
// 2:如果校验通过就返回true,否则返回false;
Matcher matcher = p.matcher(phoneNum);
return matcher.matches();
}
}

分组校验

比如当更新时需要带用户id,但当插入时不需要,这个时候就需要分组校验来处理

定义分组

1
2
3
4
import javax.validation.groups.Default;

public interface Update extends Default {
}

指定分组

1
2
3
4
5
/***
* 只有当为Update分组时才会进行校验
*/
@NotNull(message = "id必须非空",groups ={Update.class})
private Long id;

测试使用

1
2
3
4
5
6
7
8
9
@PostMapping("/insert")
public String insertTestUser(@RequestBody @Validated(value = Update.class) TestDemo testUser, BindingResult bindingResult){
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
for (ObjectError error : bindingResult.getAllErrors()) {
return error.getDefaultMessage();
}
log.info("用户名{}的邮箱为{}", testUser.getUsername(),testUser.getEmail());
return "success";
}

Tip:当不使用BindingResult时,校验失败会引发MethodArgumentNotValidException异常,这样就正好可以使用SpringBoot全局异常处理

快速失败(failFast)

SpringValidation框架默认是关闭快速失败的,也就是说当一个参数校验不通过的时候它不会直接停止,而是会继续校验剩余的参数。

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

@Bean
public Validator validator(AutowireCapableBeanFactory springFactory) {
try (ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败
.failFast(true)
// 解决 SpringBoot 依赖注入问题
.constraintValidatorFactory(new SpringConstraintValidatorFactory(springFactory))
.buildValidatorFactory()) {
return factory.getValidator();
}
}
}

参考