一、前言
在平时开发中,前端提交表单时,通常会校验一些数据的可行性,比如是否为空,长度,身份证,邮箱等等,不过这样的验证是否就足够了呢,答案肯定是否定的。一个可靠的系统,不仅仅要依靠前端的数据验证,后端的验证也是必不可少的。
以前验证数据的合法性,需要通过写大量的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依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!--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> |
- 创建一个用于需要验证的实体类:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | @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类,用于测试:
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 | @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数据校验快速失败
上面的测试结果返回了很多的校验错误信息,现在我觉得字段太多了,没必要全部校验完成,只要出现错误就返回给前端,快速失败。这样怎么实现呢?
其实也很简单,增加一个校验配置类就行了,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @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注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @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注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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类:
1 2 3 4 5 6 7 8 9 | @Data @IsOldFriend(message = "你确定这个员工是老打工人嘛") // 验证是否是合法的老员工 public class Admin {<!-- --> private Boolean isOldFirend;//是否是老员工 private LocalDateTime registerDate;// 注册时间 } |
- 测试,修改一下TestController:
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 | @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校验注解,进行参数校验。