学习Validator验证框架总结

在项目开发中许多地方需要加以验证,对于使用if-else简单粗暴一个一个验证,spring的validation封装了Javax ValidationI校验参数,大大缩减了代码量。

以前的分层验证,从controller到落入数据库,一层一层验证,代码重复、冗余。

Javax ValidationI使用Java Bean验证通过注解将约束添加到域模型中,将验证逻辑从代码中分离出来。

Javax ValidationI的依赖:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.16.Final</version>
 </dependency>

  

springboot对Javax ValidationI封装,依赖变成

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

 一、初级注解

JSR提供的验证注解:

@Null   被注释的元素必须为 null    

@NotNull    被注释的元素必须不为 null    

@AssertTrue     被注释的元素必须为 true    

@AssertFalse    被注释的元素必须为 false    

@Min(value)     被注释的元素必须是一个数字,其值必须大于等于指定的最小值   &nbsp

@Max(value)     被注释的元素必须是一个数字,其值必须小于等于指定的最大值    

@DecimalMin(value)  被注释的元素必须是一个数字,其值必须大于等于指定的最小值    

@DecimalMax(value)  被注释的元素必须是一个数字,其值必须小于等于指定的最大值    

@Size(max=, min=)   被注释的元素的大小必须在指定的范围内    

@Digits (integer, fraction)     被注释的元素必须是一个数字,其值必须在可接受的范围内    

@Past   被注释的元素必须是一个过去的日期    

@Future     被注释的元素必须是一个将来的日期    

@Pattern(regex=,flag=)  被注释的元素必须符合指定的正则表达式

Validator提供的验证注解:

@NotBlank(message =)   验证字符串非null,且长度必须大于0    

@Email  被注释的元素必须是电子邮箱地址    

@Length(min=,max=)  被注释的字符串的大小必须在指定的范围内    

@NotEmpty   被注释的字符串的必须非空    

@Range(min=,max=,message=)  被注释的元素必须在合适的范围内

二、创建验证器

/**
 * 验证测试类
 */
public class ValidationTest {

    // 验证器对象
    private Validator validator;
    // 待验证对象
    private UserInfo userInfo;
    // 验证结果集合
    private Set<ConstraintViolation<UserInfo>> set;

    /**
     * 初始化操作
     */
    @Before
    public void init() {
        // 初始化验证器
        validator = Validation.buildDefaultValidatorFactory()
                .getValidator();

        // 初始化待验证对象 - 用户信息
        userInfo = new UserInfo();

    }

    /**
     * 结果打印
     */
    @After
    public void print() {
        set.forEach(item -> {
            // 输出验证错误信息
            System.out.println(item.getMessage());
        });

    }

    @Test
    public void nullValidation() {
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo);
    }

}

  三、中级约束

    中级约束中分为分组约束、组序列

public class UserInfo {

    // 登录场景
    public interface LoginGroup {}

    // 注册场景
    public interface RegisterGroup {}

    /**
     * 用户ID
     */
    @NotNull(message = "用户ID不能为空",
            groups = LoginGroup.class)
    private String userId;

    /**
     * 用户名
     * NotEmpty 不会自动去掉前后空格
     */
    @NotEmpty(message = "用户名称不能为空",groups = RegisterGroup.class)
    private String userName;

    /**
     * 用户密码
     * NotBlank 自动去掉字符串前后空格后验证是否为空
     */
    @NotBlank(message = "用户密码不能为空")
    @Length(min = 6, max = 20,
            message = "密码长度不能少于6位,多于20位")
    private String passWord;

}

  

  /**
     * 分组验证测试方法
     */
    @Test
    public void groupValidation() {
        set = validator.validate(userInfo,
                UserInfo.RegisterGroup.class,
                UserInfo.LoginGroup.class);
    }

  组排序:

public class UserInfo {

    // 登录场景
    public interface LoginGroup {}

    // 注册场景
    public interface RegisterGroup {}

    // 组排序场景
    @GroupSequence({
            LoginGroup.class,
            RegisterGroup.class,
            Default.class
    })
    public interface Group {}

    /**
     * 用户ID
     */
    @NotNull(message = "用户ID不能为空",
            groups = LoginGroup.class)
    private String userId;

    /**
     * 用户名
     * NotEmpty 不会自动去掉前后空格
     */
    @NotEmpty(message = "用户名称不能为空", 
            groups = RegisterGroup .class)
    private String userName;

    /**
     * 用户密码
     * NotBlank 自动去掉字符串前后空格后验证是否为空
     */
    @NotBlank(message = "用户密码不能为空")
    @Length(min = 6, max = 20,
            message = "密码长度不能少于6位,多于20位")
    private String passWord;
}    

  

    /**
     * 组序列测试
     */
    @Test
    public void groupSequenceValidation() {
        set = validator.validate(userInfo,
                UserInfo.Group.class);
    }

  三、高级约束

    高级约束是对参数、返回值的约束。

    使用注解:

      javax:@Valid

      spring:@Validated

    在检验参数、返回值是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同。

    @Validated:提供了一个分组功能,可以在入参、返回值验证时,根据不同的分组采用不同的验证机制。

    @Valid:不支持分组功能。

    注解地方:

    @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上。

    @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上。

/**
 * 用户信息
 */
public class UserInfoService {

    /**
     * UserInfo 作为输入参数
     * @param userInfo
     */
    public void setUserInfo(@Valid UserInfo userInfo) {}

    /**
     * UserInfo 作为输出参数
     * @return
     */
    public @Valid UserInfo getUserInfo() {
        return new UserInfo();
    }

}

  

/**
 * 验证测试类
 */
public class ValidationTest {

    // 验证器对象
    private Validator validator;
    // 待验证对象
    private UserInfo userInfo;
    // 验证结果集合
    private Set<ConstraintViolation<UserInfoService>> otherSet;

    /**
     * 初始化操作
     */
    @Before
    public void init() {
        // 初始化验证器
        validator = Validation.buildDefaultValidatorFactory()
                .getValidator();

        // 初始化待验证对象 - 用户信息
        userInfo = new UserInfo();
    }

    /**
     * 结果打印
     */
    @After
    public void print() {
        set.forEach(item -> {
            // 输出验证错误信息
            System.out.println(item.getMessage());
        });

    }


    /**
     * 对方法输入参数进行约束注解校验
     */
    @Test
    public void paramValidation() throws NoSuchMethodException {
        // 获取校验执行器
        ExecutableValidator executableValidator =
                validator.forExecutables();

        // 待验证对象
        UserInfoService service = new UserInfoService();
        // 待验证方法
        Method method = service.getClass()
                .getMethod("setUserInfo", UserInfo.class);
        // 方法输入参数
        Object[] paramObjects = new Object[]{new UserInfo()};

        // 对方法的输入参数进行校验
        otherSet = executableValidator.validateParameters(
                service,
                method,
                paramObjects);
    }


    /**
     * 对方法返回值进行约束校验
     */
    @Test
    public void returnValueValidation()
            throws NoSuchMethodException,
            InvocationTargetException, IllegalAccessException {

        // 获取校验执行器
        ExecutableValidator executableValidator =
                validator.forExecutables();

        // 构造要验证的方法对象
        UserInfoService service = new UserInfoService();
        Method method = service.getClass()
                .getMethod("getUserInfo");

        // 调用方法得到返回值
        Object returnValue = method.invoke(service);

        // 校验方法返回值是否符合约束
        otherSet = executableValidator.validateReturnValue(
                service,
                method,
                returnValue);
    }

}

  在controller中验证入参一般使用@Validated

@RequestMapping(method = RequestMethod.POST)
    public UserInfo  create(@RequestBody @Validated( { RegisterGroup.class }) UserInfo  userInfo) {
		return userService.create(userInfo);
    }

  @RequestMapping(method = RequestMethod.GET)
    public UserInfo  getUserById(@NotNull(message = "id不能为空")  int userId)  {
       return userService.getUserById(userId);
    }

  四、自定义约束注解

/**
 * 自定义手机号约束注解
 */
@Documented
// 注解的作用目标
@Target({ElementType.FIELD})
// 注解的保留策略
@Retention(RetentionPolicy.RUNTIME)
// 不同之处:于约束注解关联的验证器
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {

    // 约束注解验证时的输出信息
    String message() default "手机号校验错误";

    // 约束注解在验证时所属的组别
    Class<?>[] groups() default {};

    // 约束注解的有效负载(严重程度)
    Class<? extends Payload>[] payload() default {};
}

  

/**
 * 自定义手机号约束注解关联验证器
 */
public class PhoneValidator
        implements ConstraintValidator<Phone, String> {

    /**
     * 自定义校验逻辑方法
     * @param value
     * @param context
     * @return
     */
    @Override
    public boolean isValid(String value,
                           ConstraintValidatorContext context) {

        // 手机号验证规则:158后头随便
        String check = "158\\d{8}";
        Pattern regex = Pattern.compile(check);

        // 空值处理
        String phone = Optional.ofNullable(value).orElse("");
        Matcher matcher = regex.matcher(phone);

        return matcher.matches();
    }
}

  自定义注解使用:

public class UserInfo{
    /**
     * 手机号
     */
    @Phone(message = "手机号不是158后头随便")
    private String phone;
}

  

posted @ 2020-05-20 16:45  一生无过  阅读(3364)  评论(0编辑  收藏  举报