一、前言
在平时开发中,前端提交表单时,通常会校验一些数据的可行性,比如是否为空,长度,身份证,邮箱等等,不过这样的验证是否就足够了呢,答案肯定是否定的。一个可靠的系统,不仅仅要依靠前端的数据验证,后端的验证也是必不可少的。
以前验证数据的合法性,需要通过写大量的if…else…条件判断,有没有更简便快捷的方法呢,答案是有的,就是使用validator进行数据校验。
spring boot的validation模块已经为我们提供了许多默认直接可以使用的注解,注意:spring boot2.3.x以后版本需要手动导入spring-boot-starter-validation。
常见注解如下:
- @AssertTrue : 用于Boolean字段,该字段只能是true;
- @AssertFalse : 用于Boolean字段,该字段只能是false;
- @CreditCardNumber : 对信用卡号做一个大致的验证;
- @DecimalMax : 以传入字符串构建一个BigDecimal,规定值要小于或等于该值;
- @DecimalMin : 只能大于或等于该值;
- @Max : 只能小于或等于该值;
- @Min :只能大于或等于该值;
- @Digits(integer = , fraction = ): 无参数,验证字符串是否合法,存在参数时,检查数字,整数位和小数位是否满足需求(整数精度小于integer ;小数部分精度小于fraction );
- @Email : 检查是否是一个有效的email地址;
- @Future : 检查该字段日期是否属于将来的日期;
- @Length : 检查字段长度是否在min到max之间,只能用于字符串;
- @NotNull : 不能为null;
- @NotBlank :不能为空,检查时会将空格忽略;
- @NotEmpty : 不能为空,验证字符串不为空或者null;
- @Past : 验证该字段日期在过去;
- @Size(min = ,max = ) : 检查该字段的size是否在min和max之间,可是字符串,数组,集合,Map等;
- @URL : 检查是否是一个有效的URL;
- @Null : 只能是null;
- @Pattern(regexp=) : 验证字符串满足正则;
- @Range(min=,max=) : 检查数字是否在范围之间,这些都包括边界值;
- @Vaild 递归验证,用于对象、数组和集合,会对对象的元素、数组的元素进行逐个校验。
二、数据校验演示
- 创建一个spring boot项目,项目结构如下:
2. 导入maven依赖:
<!--spring boot2.3.x以后版本需要手动导入spring-boot-starter-validation-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- 创建一个用于需要验证的实体类:
@Data
public class Admin {
//用于Boolean字段,该字段只能是true
@AssertTrue(message = "必须是管理员")
private Boolean isAdmin;
//用于Boolean字段,该字段只能是false
@AssertFalse(message = "不能是普通用户")
private Boolean isUser;
// 对信用卡号做一个大致的验证
@CreditCardNumber(message = "信用卡号非法")
private String creditCardNumber;
// 只能小于或等于该值
//@DecimalMax(value = "55", message = "超过最大年龄")
@Max(value = 55)
private Integer maxAge;
// 只能大于或等于该值
@DecimalMin(value = "25", message = "小于最小年龄")
private Integer minAge;
// 检查数字,整数位和小数位是否满足需求
@Digits(integer = 5, fraction = 2, message = "工资数据格式有误")
private Double salary;
// 检查是否是一个有效的email地址
@Email(message = "邮箱格式非法")
private String email;
// 检查该字段日期是否属于将来的日期
@Future(message = "过期时间设置错误")
private LocalDateTime expire;
// 检查字段长度是否在min到max之间,只能用于字符串
@Length(min = 1, max = 20, message = "管理员名称长度请在1-20之间")
private String adminName;
//不能为null
@NotNull(message = "密码不能为空")
private String password;
private Boolean isOldFirend;//是否是老员工
private LocalDateTime registerDate;// 注册时间
}
- 测试,为了方便创建一个TestController类,用于测试:
@RestController
@RequestMapping("/test")
public class TestController {
/**
* 使用@Validated注解开启对Admin对象的校验
*
* 所有Admin对象的校验信息会存储到BindingResult对象中,从bindingResult中可以获取校验的错误信息
*/
@PostMapping("validateAdmin")
public String validateAdmin(@Validated Admin admin, BindingResult bindingResult) {
String result = "success";
// 判断是否有错误信息
if (bindingResult.hasErrors()) {
StringBuffer msg = new StringBuffer();
//获取错误字段集合
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
for (int i = 0, len = fieldErrors.size(); i < len; i++) {
FieldError fieldError = fieldErrors.get(i);
String fieldName = fieldError.getField();
String message = fieldError.getDefaultMessage();
msg.append(fieldError.getObjectName()).append("的字段").append(fieldName).append("存在错误:").append(message).append("\n");
}
result = msg.toString();
}
return result;
}
}
使用Postman进行测试:
可以看到,返回了许多校验出现错误的信息。
validator数据校验快速失败
上面的测试结果返回了很多的校验错误信息,现在我觉得字段太多了,没必要全部校验完成,只要出现错误就返回给前端,快速失败。这样怎么实现呢?
其实也很简单,增加一个校验配置类就行了,代码如下:
@Configuration
public class ValidatorConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true) // 是否快速失败 true
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
再次使用Postman进行测试:
很明显,配置了快速失败以后,只要出现校验错误就快速返回了,没有进行全部校验,在字段较多的类进行校验时,可以尝试这种方式。
自定义参数校验
现在有一个新需求了,假设刚刚Admin对象中,注册时间大于10年的,isOldFirend(老员工)字段可以设置成true,否则检验失败,给前端提示。
这是复合字段验证,要两个字段一起验证,spring boot并没有给我们提供这样的注解,这就需要我们自己实现了。
参考一下@NotBlank注解:
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface NotBlank {
String message() default "{javax.validation.constraints.NotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface List {
NotBlank[] value();
}
}
- 自定义一个@IsOldFriend注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 用于描述类、接口(包括注解类型) 或enum声明
@Documented
@Constraint(validatedBy = IsOldFriendValidator.class)
public @interface IsOldFriend {
// 校验未通过时的返回信息
String message() default "该管理员不是老员工";
// 以下两行为固定模板
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
自定义注解参考spring boot提供的注解还是比较简单的,@Constraint注解中需要提供我们针对这个注解的校验类。
- 实现自定义的校验类,IsOldFriendValidator:
public class IsOldFriendValidator implements ConstraintValidator<IsOldFriend, Admin> {
@Override
public boolean isValid(Admin value, ConstraintValidatorContext context) {
boolean result = false;
// 获取注册的时间
LocalDateTime registerDate = value.getRegisterDate();
if (registerDate != null) {
long interval = ChronoUnit.YEARS.between(registerDate, LocalDateTime.now());
// 如果注册年限大于等于10年,并且标注了是老员工,两者同时满足则是合法的
if (interval >= 10 && value.getIsOldFirend() == true) {
result = true;
}
}
return result;
}
}
自定义的校验类需要实现ConstraintValidator接口,接口的第一个泛型是你自定义的注解,我这里是IsOldFriend注解,第二个泛型表示你自定义注解作用在什么类型上,如果是String类型就改成String,我这里是作用在Admin对象上,所以是Admin。
- 使用自定义注解,为了演示效果,我精简一下Admin类:
@Data
@IsOldFriend(message = "你确定这个员工是老打工人嘛") // 验证是否是合法的老员工
public class Admin {
private Boolean isOldFirend;//是否是老员工
private LocalDateTime registerDate;// 注册时间
}
- 测试,修改一下TestController:
@RestController
@RequestMapping("/test")
public class TestController {
/**
* 使用@Validated注解开启对Admin对象的校验
*
* 所有Admin对象的校验信息会存储到BindingResult对象中,从bindingResult中可以获取校验的错误信息
*/
@PostMapping("validateAdmin")
public String validateAdmin(@Validated Admin admin, BindingResult bindingResult) {
String result = "success";
// 判断是否有错误信息
if (bindingResult.hasErrors()) {
StringBuffer msg = new StringBuffer();
List<ObjectError> errors = bindingResult.getAllErrors();
for (int i = 0, len = errors.size(); i < len; i++) {
ObjectError error = errors.get(i);
msg.append(error.getObjectName()).append("存在错误:").append(error.getDefaultMessage()).append("\n");
}
result = msg.toString();
}
return result;
}
}
使用Postman测试结果:
可以看到,自定义的注解校验是生效了的。
三、总结
本文主要介绍了:
- spring boot2.x 使用validator进行数据校验;
- 使用validator进行数据校验时,如果快速失败;
- 自定义validator校验注解,进行参数校验。