SpringBoot + validator优雅参数校验,消除if-else
Hi,大家好,我是抢老婆酸奶的小肥仔。
在我们日常开发中,参数校验必不可少,一般情况下我们习惯采用if-else来进行校验,结果就是满屏的if-else。代码既不优雅,也会令人眼花缭乱,维护也是繁琐。那么有没有优雅的参数校验呢?有,使用hibernate-validator
吧,它会让你爱不释手。
废话不多说,撸起来吧。
1、简介
Hibernate Validate是Bean Validation实现的,内置了JSR303/JSR380中所有的constraint的实现,同时也额外提供了很多自定义的constraint。Bean Validation为JavaBean的验证提供了很多相关的元数据模型和API。
参数说明
在Hibernate Validate提供了很多注解,以实现对应参数的校验。
参数 | 描述 |
---|---|
@Null | 参数必须为null |
@NotNull | 参数不能为null |
@NotBlank | 参数不能为null和空值,一般作用在字符串类型 |
@AssertTrue | 参数必须为true |
@AssertFalse | 参数必须为false |
@Min(value) | 参数必须是数字,且大于等于指定的最小值 |
@Max(value) | 参数必须是数字,且小于等于指定的最大值 |
@DecimalMin(value) | 参数必须是数字,且大于等于指定的最小值 |
@DecimalMax(value) | 参数必须是数字,且小于等于指定的最大值 |
@Length(min,max) | 指定字符长度,最小值是多少,最大值是多少 |
@Size(max,min) | 参数的大小必须在指定的范围 |
@Digits(Integer,fraction) | 参数必须是个数字,且大小必须在可接受范围内 |
@Past | 参数必须是一个过去的日期 |
@Future | 参数必须是一个将来的日期 |
@Pattern(value) | 参数必须符合指定的正则表达式 |
参数必须符合电子邮箱地址 | |
@NotEmpty | 参数必须非空 |
@Range | 参数必须在合适的范围内 |
2、快速开始
在SpringBoot中使用Hibernate Validate特别简单,只需要引入jar包即可。
mavan引入:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.3.Final</version>
</dependency>
gradle引入:
implementation group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.2.3.Final'
封装统一异常处理类:
/**
* @author: jiangjs
* @description: 统一异常处理方法
* @date: 2022/6/21 11:28
**/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({ConstraintViolationException.class, MethodArgumentNotValidException.class})
public ResultUtil<String> resolveViolationException(Exception ex){
StringJoiner messages = new StringJoiner(",");
if (ex instanceof ConstraintViolationException){
Set<ConstraintViolation<?>> violations = ((ConstraintViolationException) ex).getConstraintViolations();
for (ConstraintViolation<?> violation : violations) {
messages.add(violation.getMessage());
}
} else {
List<ObjectError> allErrors = ((MethodArgumentNotValidException) ex).getBindingResult().getAllErrors();
for (ObjectError error : allErrors) {
messages.add(error.getDefaultMessage());
}
}
return ResultUtil.error(400,String.valueOf(messages));
}
}
2.1 参数校验
2.1.1 创建实体
/**
* @author: jiangjs
* @description:
* @date: 2022/6/21 10:46
**/
@Data
public class UserInfo {
private Long userId;
@NotBlank(message = "用户名不能为空")
private String userName;
@NotBlank(message = "密码不能为空")
private String password;
@Email(message = "请填写正确的email")
@NotBlank(message = "email不能为空")
private String email;
}
上述实体中,我们看到使用了@NutBlank,@Email来校验String参数不能null和空,同时也校验了email是否符合email规则。
2.1.2 测试
使用Hibernate Validate则直接在参数前使用 @Validated
注解即可。
@PostMapping("/insertUserInfo.do")
public ResultUtil<?> insertUserInfo(@Validated @RequestBody UserInfo userInfo){
return ResultUtil.success(userInfo.toString());
}
输出:
在参数校验时,会根据参数的顺序来进行校验,当一个参数不符合规则时,则直接返回校验结果。
- 我们在进行参数传递时,不填写username。
- 校验email是否符合规则
2.2 路径参数校验
我们在使用restful定义接口时,可能会使用@PathVariable来直接在接口url后传递参数,有时候传递的参数并不符合我们定义的参数规则,此时我们可以进行参数校验。
校验规则:参数:正则表达式
@GetMapping("/getUserInfoByUserId.do/{userId:[0-9_]+}")
public String getUserInfoByUserId(@PathVariable("userId") Integer userId){
return String.valueOf(userId);
}
定义传递的userId必须是由数字组成。
输出:
当传递参数不符合正则时,则直接报错。
通过url进行参数校验,我们只看到了报错,并没有提示具体错误信息,也不能像使用注解那样自定义错误信息返回。
2.3 分组校验
在我们日常开发中常常涉及到更新,在更新的时候往往需要传递数据的Id,但是新增的时候又不需要这个Id,此时我们可以采用分组来进行数据Id的校验。
2.3.1 分组定义
/**
* @author: jiangjs
* @description:
* @date: 2022/6/21 10:46
**/
@Data
public class UserInfo {
public interface Update{};
@NotNull(message = "userId不能为空",groups = Update.class)
private Long userId;
@NotBlank(message = "用户名不能为空")
private String userName;
@NotBlank(message = "密码不能为空")
private String password;
@Email(message = "请填写正确的email")
@NotBlank(message = "email不能为空")
private String email;
}
我们在实体类中定义了一个接口Update,然后在注解中的groups中指定即可。
2.3.2 测试
/**
* 分组校验数据信息:定义Update
*/
@PostMapping("/updateUserInfo.do")
public ResultUtil<?> updateUserInfo(@Validated(value = UserInfo.Update.class) @RequestBody UserInfo userInfo){
return ResultUtil.success(userInfo.toString());
}
在@Validated中value指定实体中定义的接口。
输出:
2.4 校验传递
在日常开发中我们经常会在实体中引入其他实体,并且又要对其他实体中的数据进行数据校验,此时就存在检验传递,通常我们在需要校验的实体前使用 @Valid
即可。
2.4.1 定义实体
添加一个雇员信息实体:
/**
* @author: jiangjs
* @description:
* @date: 2022/6/21 14:48
**/
@Data
public class Employee {
@NotBlank(message = "请填写雇员名称")
private String name;
}
在userInfo实体中引入Employee
/**
* @author: jiangjs
* @description:
* @date: 2022/6/21 10:46
**/
@Data
public class UserInfo {
public interface Update{};
@NotNull(message = "userId不能为空",groups = Update.class)
private Long userId;
@NotBlank(message = "用户名不能为空")
private String userName;
@NotBlank(message = "密码不能为空")
private String password;
@Email(message = "请填写正确的email")
@NotBlank(message = "email不能为空")
private String email;
@Valid
@NotNull(message = "雇员信息不能为空")
private Employee employee;
}
2.4.2 测试
@PostMapping("/insertUserInfo.do")
public ResultUtil<?> insertUserInfo(@Validated @RequestBody UserInfo userInfo){
return ResultUtil.success(userInfo.toString());
}
输出:
2.5 自定义校验
在参数校验时,可能Hibernate Validate自带的校验规则并不能满足我们业务需求,此时我们可以实现自定义校验规则。
需求:对身份证进行校验。
2.5.1 定义注解
/**
* @author: jiangjs
* @description: 身份证校验规则
* @date: 2022/6/21 11:28
**/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Constraint(validatedBy = IdCodeValidator.class)
public @interface IdCode {
String message() default "请填写正确的身份证信息";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Constraint
:Hibernate Validate提供的注解,用于实现自定义校验规则。其中validatedBy
就是指定进行参数校验的实现类。
注:在自定义校验注解时必须带上groups()和payload(),否则会报错。
2.5.2 校验实现类
/**
* @author: jiangjs
* @description: 身份证校验
* @date: 2022/6/21 14:25
**/
public class IdCodeValidator implements ConstraintValidator<IdCode,Object> {
/**
* 18位二代身份证号码的正则表达式
*/
public static final String REGEX_ID_NO_18 = "^"
// 6位地区码
+ "\d{6}"
// 年YYYY
+ "(18|19|([23]\d))\d{2}"
// 月MM
+ "((0[1-9])|(10|11|12))"
// 日DD
+ "(([0-2][1-9])|10|20|30|31)"
// 3位顺序码
+ "\d{3}"
// 校验码
+ "[0-9Xx]"
+ "$";
/**
* 15位一代身份证号码的正则表达式
*/
public static final String REGEX_ID_NO_15 = "^"
+ "\d{6}" // 6位地区码
+ "\d{2}" // 年YYYY
+ "((0[1-9])|(10|11|12))" // 月MM
+ "(([0-2][1-9])|10|20|30|31)" // 日DD
+ "\d{3}"// 3位顺序码
+ "$";
@Override
public void initialize(IdCode constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
String idCode = String.valueOf(o);
if (idCode.length() != 15 && idCode.length() != 18){
return false;
}
return idCode.matches(REGEX_ID_NO_18) || idCode.matches(REGEX_ID_NO_15);
}
}
ConstraintValidator:Hibernate Validate提供的接口,ConstraintValidator<A extends Annotation, T>,第一个参数必须是注解,即Annotation,第二个参数是泛型。同时在接口中提供了两个方法:
- initialize(A constraintAnnotation),初始化注解,
- boolean isValid(T var1, ConstraintValidatorContext var2):校验参数,true表示校验通过,false表示校验失败。
2.5.3 测试
/**
* 自定义校验数据信息
*/
@PostMapping("/definedEmployeeInfo.do")
public ResultUtil<?> definedEmployeeInfo(@Validated @RequestBody Employee employee){
return ResultUtil.success(employee.toString());
}
在Employee中添加校验注解:
/**
* @author: jiangjs
* @description:
* @date: 2022/6/21 14:48
**/
@Data
public class Employee {
@NotBlank(message = "请填写雇员名称")
private String name;
@IdCode(message = "请填写正确的身份证信息!")
private String idCode;
}
输出:
输入了错误的身份证,则校验不通过,提示定义的信息。
在日常工作中,使用Hibernate Validate进行参数校验真的会事半功倍,同时也提供了自定义校验的实现,让我们更加专注于业务。
好了,今天的分享就先到这,谢谢大家听我唠叨。
转载自:https://juejin.cn/post/7246800194463957053