0%

Hibernate-Validator后端参数校验

文章字数:809,阅读全文大约需要3分钟

整理自原文

环境

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.9.Final</version>
</dependency>

如果使用的是SpringBoot,这个依赖是自带的。

校验Controller入参的对象

  1. 添加校验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data // get set
@AllArgsConstructor // 创建有所有参数的构造方法
@NoArgsConstructor // 创建无参构造方法
public class Account {
private String id;
@NotNull
@Length(max = 20)
private String userName;
@NotNull
@Pattern(regexp = "[A-Z][a-z][0-9]")
private String passWord;
@DateTimeFormat(pattern = "yyy-MM-dd") // String转换成时间
private Date createTime;
private String alias;
@Max(10)
@Min(1)
private Integer level;
private Integer vip;
}
  1. 使用
1
2
3
4
5
6
// 需要加 @Valid 才会校验,错误直接抛出异常
@PostMapping("/saveAccount")
public Object saveAccount(@RequestBody @Valid Account account){
accountService.saveAccount(account);
return "保存成功";
}

Controller方法上直接校验

在类上需要添加注解@Validated

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping("/validation")
@RestController
@Validated
public class ValidationController {
/**如果只有少数对象,直接把参数写到Controller层,然后在Controller层进行验证就可以了。*/
@RequestMapping(value = "/demo3", method = RequestMethod.GET)
public void demo3(@Range(min = 1, max = 9, message = "年级只能从1-9")
@RequestParam(name = "grade", required = true)
int grade,
@Min(value = 1, message = "班级最小只能1")
@Max(value = 99, message = "班级最大只能99")
@RequestParam(name = "classroom", required = true)
int classroom) {
System.out.println(grade + "," + classroom);
}
}

使用工具类校验

  1. 工具类

    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
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Set;
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;

    import lombok.Data;
    import org.hibernate.validator.HibernateValidator;
    public class ValidationUtil {
    /**
    * 开启快速结束模式 failFast (true)
    */
    private static Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
    /**
    * 校验对象
    *
    * @param t bean
    * @param groups 校验组
    * @return ValidResult
    */
    public static <T> ValidResult validateBean(T t,Class<?>...groups) {
    ValidResult result = new ValidationUtil().new ValidResult();
    Set<ConstraintViolation<T>> violationSet = validator.validate(t,groups);
    boolean hasError = violationSet != null && violationSet.size() > 0;
    result.setHasErrors(hasError);
    if (hasError) {
    for (ConstraintViolation<T> violation : violationSet) {
    result.addError(violation.getPropertyPath().toString(), violation.getMessage());
    }
    }
    return result;
    }
    /**
    * 校验bean的某一个属性
    *
    * @param obj bean
    * @param propertyName 属性名称
    * @return ValidResult
    */
    public static <T> ValidResult validateProperty(T obj, String propertyName) {
    ValidResult result = new ValidationUtil().new ValidResult();
    Set<ConstraintViolation<T>> violationSet = validator.validateProperty(obj, propertyName);
    boolean hasError = violationSet != null && violationSet.size() > 0;
    result.setHasErrors(hasError);
    if (hasError) {
    for (ConstraintViolation<T> violation : violationSet) {
    result.addError(propertyName, violation.getMessage());
    }
    }
    return result;
    }
    /**
    * 校验结果类
    */
    @Data
    public class ValidResult {

    /**
    * 是否有错误
    */
    private boolean hasErrors;

    /**
    * 错误信息
    */
    private List<ErrorMessage> errors;

    public ValidResult() {
    this.errors = new ArrayList<>();
    }
    public boolean hasErrors() {
    return hasErrors;
    }

    public void setHasErrors(boolean hasErrors) {
    this.hasErrors = hasErrors;
    }

    /**
    * 获取所有验证信息
    * @return 集合形式
    */
    public List<ErrorMessage> getAllErrors() {
    return errors;
    }
    /**
    * 获取所有验证信息
    * @return 字符串形式
    */
    public String getErrors(){
    StringBuilder sb = new StringBuilder();
    for (ErrorMessage error : errors) {
    sb.append(error.getPropertyPath()).append(":").append(error.getMessage()).append(" ");
    }
    return sb.toString();
    }

    public void addError(String propertyName, String message) {
    this.errors.add(new ErrorMessage(propertyName, message));
    }
    }

    @Data
    public class ErrorMessage {

    private String propertyPath;
    private String message;

    public ErrorMessage() {
    }

    public ErrorMessage(String propertyPath, String message) {
    this.propertyPath = propertyPath;
    this.message = message;
    }
    }
    }
  2. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void test5() throws IOException {
    Account account = new Account();
    account.setAlias("kalakala");
    account.setUserName("wokalakala");
    account.setPassWord("密码");
    ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(account);
    if(validResult.hasErrors()){
    String errors = validResult.getErrors();
    System.out.println(errors);
    }
    }

自定义规则

  1. 注解上必须有 @Constraint(validatedBy = {**.class}) 注解标注,validateBy 的值就是校验逻辑的实现类,实现类必须实现接口ConstraintValidator
  2. 自定义注解 必须包含 message ,groups,payload 属性。
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import org.apache.commons.lang3.time.DateUtils;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.text.ParseException;
import java.util.Date;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Created by hu on 2018/3/12.
*/
@Target({FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {DateValidator.DateValidatorInner.class})
public @interface DateValidator {

/**
* 必须的属性
* 显示 校验信息
* 利用 {} 获取 属性值,参考了官方的message编写方式
*@see org.hibernate.validator 静态资源包里面 message 编写方式
*/
String message() default "日期格式不匹配{dateFormat}";

/**
* 必须的属性
* 用于分组校验
*/
Class<?>[] groups() default {};

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

/**
* 非必须
*/
String dateFormat() default "yyyy-MM-dd HH:mm:ss";

/**
* 必须实现 ConstraintValidator接口
*/
class DateValidatorInner implements ConstraintValidator<DateValidator, String> {
private String dateFormat;

@Override
public void initialize(DateValidator constraintAnnotation) {
this.dateFormat = constraintAnnotation.dateFormat();

}

/**
* 校验逻辑的实现
* @param value 需要校验的 值
* @return 布尔值结果
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if("".equals(value)){
return true;
}
try {
Date date = DateUtils.parseDate(value, dateFormat);
return date != null;
} catch (ParseException e) {
return false;
}
}
}
}

使用

1
2
@DateValidator(dateFormat = "yyyy-MM-dd")
private String day;

分组校验

同一个对象在不同业务下验证规则可能不一样

  1. 声明规则时指定分组(不写默认是Default.class分组)
    1
    2
    @DateValidator(dateFormat = "yyyy-MM-dd",groups = {AccountService.class})
    private String birthday;
  2. 验证规则时加入分组
    1
    2
    3
    4
    5
    ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(account, AccountService.class);
    if(validResult.hasErrors()){
    String errors = validResult.getErrors();
    System.out.println(errors);
    }

其它常用规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@AssertFalse @AssertTrue  检验boolean类型的值

@DecimalMax @DecimalMin 限定被标注的属性的值的大小

@Digits(intege=,fraction=) 限定被标注的属性的整数位数和小数位数

@Future检验给定的日期是否比现在晚

@Past 校验给定的日期是否比现在早

@Max检查被标注的属性的值是否小于等于给定的值

@Min检查被标注的属性的值是否大于等于给定的值

@NotNull检验被标注的值不为空

@Null 检验被标注的值为空

@Pattern(regex=,flag=) 检查该字符串是否能够在match指定的情况下被regex定义的正则表达式匹配

@Size(min=,max=) 检查被标注元素的长度

@Valid递归的对关联的对象进行校验

配合使用的全局异常处理

1
2
3
4
5
6
7
8
9
10
11
@RestControllerAdvice
public class ExceptionControllerAdvice {

@ExceptionHandler(MethodArgumentNotValidException.class)
public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 然后提取错误提示信息进行返回
return objectError.getDefaultMessage();
}
}

不抛异常,手动判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;

@PostMapping("/addUser")
public String addUser(@RequestBody @Valid User user, BindingResult bindingResult) {
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
for (ObjectError error : bindingResult.getAllErrors()) {
return error.getDefaultMessage();
}
return userService.addUser(user);
}
}