十、验证框架

分层验证与JavaBean验证

分层验证模型

Bean Validation简介

Bean Validation为Java Bean验证定义了相应的元数据模型和API

JCP,JSR简介

JCP(Java Community Process)成立于1998年,是使有兴趣的各方参与定义Java的特征和未来版本的正式过程。
JCP使用JSR(Java 规范请求,Java Specification Requests)作为正式规范文档,描述被提议加入到Java体系中的规范和技术

常用约束注解

空值校验类

  • @Null 必须为null
  • @NotNull 不能为null,但可以为empty,用在基本类型上
  • @NotEmpty 不能为null,而且长度必须大于0,用在CharSequence子类型、Collection、Map、数组
  • @NotBlank 不能为null,只能作用在String上,而且调用trim()后,长度必须大于0

范围校验类

  • @Min 必须是一个数字,其值必须大于等于指定的最小值
  • @Max 必须是一个数字,其值必须小于等于指定的最大值
  • @Size 大小必须在指定的范围内,作用字符串、Collection、Map、数组等
  • @Length 长度必须在指定的范围内,作用CharSequence子类型
  • @Digits 必须是一个数字,其值必须在可接受的范围内
  • @Future 必须是一个将来的日期
  • @Past 不能是一个将来的日期
  • @Negative 是不是负数,0不能通过验证

其他校验类

  • @Email 必须是电子邮件地址
  • @URL 必须为有效的 URL
  • @AssertTrue 必须为true
  • @Pattern 必须符合指定的正则表达式

依赖

JSR 380是用于bean验证的Java API的规范,是JavaEE和JavaSE的一部分。
根据JSR 380规范,validation-api依赖包含标准验证API:

        <!--验证API-->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>

Hibernate Validator是验证API的参考实现。
要使用它,我们必须添加以下依赖项:

        <!--验证API参考实现-->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.1.2.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator-annotation-processor</artifactId>
            <version>6.1.2.Final</version>
        </dependency>

JSR 380提供对变量插值的支持,允许在违规消息中使用表达式。

要解析这些表达式,我们必须在表达式语言API和该API的实现上添加依赖项。GlassFish提供参考实现:

        <!--表达式语言依赖关系-->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>3.0.1-b06</version>
        </dependency>
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>3.0.1-b06</version>
        </dependency>

中级验证

级联验证

  • @Valid 指定递归验证关联的对象;如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证
   /**
     * 用户好友列表
     * @Valid验证集合中的对象
     */
    @NotEmpty(message = "好友不能为空")
    @Size(min = 1,message = "好友不能少于一个")
    private List<@Valid UserInfo> friends;

分组验证

定义接口区分不同场景的分组,然后在在注解验证的groups上指定场景的接口,在需要使用验证的地方指定分组 。

   /**
     * 分组验证
     * 定义两个接口分别对登录和注册分组
     */
    public interface LoginGroup{}
    public interface RegisterGroup{}

    /**
     * 用户ID
     */
    @NotNull(message = "用户ID不能为空",groups = RegisterGroup.class)
    private String userId;
    /**
     * 用户名字
     */
    @NotEmpty(message = "用户名称不能为空",groups = LoginGroup.class)
    private String userName;
    /**
     * 用户密码
     */
    @NotBlank(message = "用户密码不能为空" ,groups = {LoginGroup.class,RegisterGroup.class})
    @Length(min = 6,max = 20,message = "密码长度不能小于6位,不能大于20位")
    private String passWord;

组序列

多个分组指定分组的验证顺序,使用@GroupSequence注解

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

实例

/**
 * @author fangliu
 * @date 2020-02-22
 * @description 待验证的Bean对象
 */
@Data
public class UserInfo {
    /**
     * 分组验证
     * 定义两个接口分别对登录和注册分组
     */
    public interface LoginGroup{}
    public interface RegisterGroup{}

    /**
     * 组序列场景
     * 多个分组指定分组的验证顺序
     */
    @GroupSequence({
            LoginGroup.class,
            RegisterGroup.class,
            Default.class
    })
    public interface Group{}

    /**
     * 用户ID
     */
    @NotNull(message = "用户ID不能为空",groups = RegisterGroup.class)
    private String userId;
    /**
     * 用户名字
     */
    @NotEmpty(message = "用户名称不能为空",groups = LoginGroup.class)
    private String userName;
    /**
     * 用户密码
     */
    @NotBlank(message = "用户密码不能为空" ,groups = {LoginGroup.class,RegisterGroup.class})
    @Length(min = 6,max = 20,message = "密码长度不能小于6位,不能大于20位")
    private String passWord;
    /**
     * 用户邮箱
     */
    @NotNull(message = "邮箱不能为空",groups = RegisterGroup.class)
    @Email(message = "邮箱必须为有效邮箱")
    private String email;
    /**
     * 用户手机号
     */
    private String phone;
    /**
     * 用户年龄
     */
    @NotNull(message = "年龄不能为空")
    @Min(value = 18,message = "年龄不能小于18岁")
    @Max(value = 60,message = "年龄不能大于60岁")
    private Integer age;
    /**
     * 用户生日
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @Past(message = "不能是一个将来的时间")
    private Date birthday;
    /**
     * 用户好友列表
     * @Valid验证集合中的对象
     */
    @NotEmpty(message = "好友不能为空")
    @Size(min = 1,message = "好友不能少于一个")
    private List<@Valid UserInfo> friends;


}
/**
 * @author fangliu
 * @date 2020-02-22
 * @description validation验证的测试类
 */
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();
        //userInfo.setUserId("1111");
        userInfo.setUserName("张三");
        userInfo.setPassWord("123567");
        userInfo.setEmail("6666666@qq.com");
        userInfo.setAge(18);
       /* LocalDateTime time = LocalDateTime.now();
        Instant instant = time.atZone(ZoneId.systemDefault()).toInstant();
        Date date1 = Date.from(instant);*/

        LocalDate localDate = LocalDate.of(2020, 1, 5);
        ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
        Instant instant1 = zonedDateTime.toInstant();
        Date date2 = Date.from(instant1);
        userInfo.setBirthday(date2);
        userInfo.setFriends(new ArrayList(){
            {
                add(userInfo);
            }
        });
    }

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

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

    /**
     * 分组验证结果
     */
    @Test
    public void groupTest(){
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo,UserInfo.RegisterGroup.class);
    }

    /**
     * 分组排序验证结果
     */
    @Test
    public void groupSequenceTest(){
        // 使用验证器对对象进行验证
        set = validator.validate(userInfo,UserInfo.Group.class);
    }
}

高级校验

校验参数

校验返回值

校验构造方法

实例

/**
 * @author fangliu
 * @date 2020-02-22
 * @description 用户信息服务类
 */
public class UserInfoService {
    /**
     * UserInfo作为输入参数
     * @param userInfo 输入参数
     */
    public void setUserInfo(@Valid UserInfo userInfo){

    }

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

    /**
     * 无参数构造函数
     */
    public UserInfoService() {
    }

    /**
     * 接受UserInfo作为参数的构造函数
     * @param userInfo
     */
    public UserInfoService(@Valid UserInfo userInfo) {
    }
}
/**
 * @author fangliu
 * @date 2020-02-22
 * @description validation验证的测试类
 */
public class ValidationTest {
    //验证器对象
    private Validator validator;
    //待验证对象
    private UserInfo userInfo;
    //验证错误的结果集合
    private Set<ConstraintViolation<UserInfoService>> set1;
    /**
     * 初始化操作
     */
    @Before
    public void init(){
        // 初始化验证器
        validator = Validation.buildDefaultValidatorFactory().getValidator();
        // 初始化验证对象
        userInfo = new UserInfo();
        //userInfo.setUserId("1111");
        userInfo.setUserName("张三");
        userInfo.setPassWord("123567");
        userInfo.setEmail("6666666@qq.com");
        userInfo.setAge(18);
       /* LocalDateTime time = LocalDateTime.now();
        Instant instant = time.atZone(ZoneId.systemDefault()).toInstant();
        Date date1 = Date.from(instant);*/

        LocalDate localDate = LocalDate.of(2020, 1, 5);
        ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
        Instant instant1 = zonedDateTime.toInstant();
        Date date2 = Date.from(instant1);
        userInfo.setBirthday(date2);
        userInfo.setFriends(new ArrayList(){
            {
                add(userInfo);
            }
        });
    }

    /**
     * 验证结果
     */
    @After
    public void print(){
        set1.forEach(info->
            System.out.println(info.getMessage())
        );
    }
    /**
     * 对方法输入参数进行校验
     * 在实现方法入参处使用@Valid
     */
    @Test
    public void paramsValidationTest() throws NoSuchMethodException {
        // 获取校验执行器
        ExecutableValidator executableValidator = validator.forExecutables();
        // 待验证对象
        UserInfoService service = new UserInfoService();
        // 待验证方法
        Method method = service.getClass().getMethod("setUserInfo", UserInfo.class);
        // 方法输入参数
        Object[] paramObjects = {new UserInfo()};
        // 对方法的输入进行参数校验
        set1 = executableValidator.validateParameters(service, method, paramObjects);
    }

    /**
     * 对方法对返回值进行约束校验
     * 在实现方法返回值处使用@Valid
     */
    @Test
    public void returnValidationTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 获取校验执行器
        ExecutableValidator executableValidator = validator.forExecutables();
        // 待验证对象
        UserInfoService service = new UserInfoService();
        // 待验证方法
        Method method = service.getClass().getMethod("getUserInfo");
        // 调用方法得到返回值
        Object returnValue = method.invoke(service);
        // 校验方法返回值是否符合约束
        set1 = executableValidator.validateReturnValue(service, method, returnValue);
    }


    /**
     * 对构造函数的入参进行校验
     * 在构造方法入参处使用@Valid
     */
    @Test
    public void constructorTest() throws NoSuchMethodException {
        // 获取校验执行器
        ExecutableValidator executableValidator = validator.forExecutables();
        // 待验证方法
        Constructor constructor = UserInfoService.class.getConstructor(UserInfo.class);
        // 方法输入参数
        Object[] paramObjects = {new UserInfo()};
        // 校验构造函数的入参是否符合约束
        set1 = executableValidator.validateConstructorParameters(constructor,paramObjects);
    }
}

自定义手机号约束注解

1. 自定义校验逻辑方法

/**
 * @author fangliu
 * @date 2020-02-23
 * @description 自定义手机号约束注解关联验证器
 */
public class PhoneValidator implements ConstraintValidator<Phone,String> {

    /**
     * 自定义校验逻辑方法
     * @param s
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        // 手机号校验规则:158开始
        String check = "158\\d{8}";
        Pattern regex = Pattern.compile(check);
        String phone = Optional.ofNullable(s).orElse("");
        Matcher matcher = regex.matcher(phone);
        return matcher.matches();
    }
}

2. 自定义手机号约束注解

/**
 * 自定义手机号约束注解
 */
@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 {};
}

3. 使用自定义注解

    /**
     * 用户手机号
     */
    @Phone(message = "手机号以158开始")
    private String phone;
posted @ 2020-11-18 21:00  柳小白  阅读(165)  评论(0编辑  收藏  举报