我们在写Controller或者Service时经常在方法中写入大量的数据校验代码,如下

       if(StringUtils.isEmpty(pass)){
            return Result.error(ErrorCodeEnum.PASSWORD_EMPTY);
        }else if(!ValidatorUtil.isMobile(mobile)){
            return Result.error(ErrorCodeEnum.MOBILE_PATTERN_WRONG);
        }

不仅浪费时间,还让业务逻辑代码更复杂,下面我们使用JSR303为我们提供的校验工具避免过多的代码校验

JSR303

JSR 303 – Bean Validation 是一个数据验证的规范,不符合规范的将返回一条具体的信息说明该规范的错误
  • 有以下注解为我们提供校验
@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)    被注释的元素必须符合指定的正则表达式
  • 例如:一个封装的登录信息类LoginVo
@Data
public class LoginVo {
    @NotNull
    @Length(max = 11,min = 11)
    private String mobile;//手机号

    @NotNull
    @Length(min = 32)
    private String password;//密码
}
我们使用@NotNull注解为该字段声明为非空,也就是前端传过来的该参数必须有具体的值,不能为null

@Length注解为字段声明字符串最长最短的临界条件,前端传来的数据必须符合要求

  • 最后我们在使用到登录封装类的地方(如Controller参数)打上@Valid注解,就会帮我们的封装类进行校验
public Result doLogin( @Valid LoginVo loginVo)

自定义验证器

为了深入了解JSR303,我们将实现一个自定义验证器(自定义注解实现具体的验证代码),观察它是如何工作的
  • 现在我们需要为登录信息类里的mobile字段进行手机号校验(校验是否为手机号,格式错误返回格式错误信息)
  • 我们将其声明为@IsMobile注解

通过观察上述众多注解,发现验证器的一般代码如下

  • validatedBy:就是要传入一个具体的自定义验证器类(实现验证器逻辑)
  • message方法:这里返回具体的校验失败的错误信息
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(IsMobile.List.class)
@Documented
@Constraint(
        validatedBy = {MobileValidator.class}//这里的validatedBy就是要传入一个具体的自定义验证器类(实现验证器逻辑)
)
public @interface IsMobile {

    boolean required() default true;//可以使用该字段声明是否必须

    String message() default "手机号格式不合法";//这里返回具体的校验失败的错误信息

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

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

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        IsMobile[] value();
    }
}
  • 所以我们需要编写一个类实现验证器逻辑MobileValidator

    验证器需要实现ConstraintValidator接口,该接口用泛型指定两个参数

    • 第一个参数A:就是自定义注解的类
    • T就是具体需要校验的值的类型

重写其中的isValid方法

public class MobileValidator implements ConstraintValidator<IsMobile, String> {

    private boolean required = true;
    @Override
    public void initialize(IsMobile constraintAnnotation) {

    }
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if(required){//如果必须 说明以传入  则进行效验
            return ValidatorUtil.isMobile(s);
        }else {//如果不必须 说明可以没有 先判断是否为空再校验
            if (StringUtils.isEmpty(s)) {
                return false;
            } else return ValidatorUtil.isMobile(s);
        }
    }
}
  • ValidatorUtil 工具类 用户验证
public class ValidatorUtil {
    //使用正则匹配1开头的11位数字
    private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}");
//如果匹配返回true,手机号格式正确
    public static boolean isMobile(String s){
        if(StringUtils.isEmpty(s)){
            return false;
        }
        Matcher m = mobile_pattern.matcher(s);
        return m.matches();
    }
}

dzou
24 声望2 粉丝

一位不想学习算法的后端同学