zoukankan      html  css  js  c++  java
  • Bean Validation规范

    以下内容转载自:https://www.ibm.com/developerworks/cn/java/j-lo-beanvalid/

    Bean Validation规范介绍

    JSR303 规范(Bean Validation 规范)提供了对 Java EE 和 Java SE 中的 Java Bean 进行验证的方式。该规范主要使用注解的方式来实现对 Java Bean 的验证功能,并且这种方式会覆盖使用 XML 形式的验证描述符,从而使验证逻辑从业务代码中分离出来。javax.validation是JSR303规范,而hibernate-validator是则是规范的具体实现。

    POM文件中引入hibernate-validator即可使用JSR303规范:

          <dependency>
              <groupId>org.hibernate</groupId>
              <artifactId>hibernate-validator</artifactId>
              <version>4.3.0.Final</version>
          </dependency>

    或者普通工程添加相关jar包:hibernate-validator 、jboss-logging 、 validation-api这些包吧.

                           image

    入门案例:

    先将最基本的Bean Validation封装成一个简单的工具类,方便使用,案例学习。

    Validation API简单封装的ValidationUtils.java

    public class ValidationUtils {
    
        public static Validator getValidator(){
            return validator;
        }
    
        static Validator validator;
        static{
            ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
            validator=validatorFactory.getValidator();
        }
    }

    定义我们要校验的Java Bean:

    Employee.java类

    @Setter
    @Getter
    @NoArgsConstructor  //lombok注解 节约篇幅 代码整洁
    public class Employee {
    
        @NotNull(message = "员工姓名不能为空")
        @Size(min = 1,max = 10,message = "员工名字长度必须在10个字母以内")
        private String name;
        @NotNull(message = "员工ID不能为空")
        private Integer id;
    }

    使用方式:

    public static void main(String[] args) {
            Employee employee = new Employee();
            employee.setName("lvbinbin6666");
            Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
            for (ConstraintViolation constraintViolation:violations) {
                System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
                System.out.println("报错原因为:"+constraintViolation.getMessage());
            }
    }

    查看结果,可以看到这就是Bean Validation的简单使用,基于注解方式还是很方便的。

    image

    验证流程

    完成Java Bean的验证流程通常分为四个步骤:

    1.约束注解定义

    2.约束验证器验证规则定义

    3.约束注解声明

    4.约束验证流程

    自定义实现JSR303规范----@NotEmpty

    Bean Vadalition中并没有字符串为空的注解验证,Hibernate-validator扩展了@NotEmpty来校验 字符串不为空 ,自己实现一个字符串不为空的校验规则

    按照上面的流程,首先需要约束注解的定义,仿照@NotNull复制一个NotEmpty;

    import static java.lang.annotation.ElementType.*; //静态导包
    
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) //注解应用的目标类型 
    @Retention(RetentionPolicy.RUNTIME)                                 //注解应用时期
    @Documented
    @Constraint(validatedBy = {NotEmptyValidator.class})                                        //注解关联的验证器,JSR303的注解
    public @interface NotEmpty {
        String message() default "";                                     //验证时输出信息
    
        Class<?>[] groups() default { };                                 //验证时所属的组别
    
        Class<? extends Payload>[] payload() default {};
    }

    其中ValidatedBy就是自定义的验证器的指向。第二步编写自定义验证器NotEmptyValidator

    public class NotEmptyValidator implements ConstraintValidator<NotEmpty,String> {
        @Override
        public void initialize(NotEmpty constraintAnnotation) {
    
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if(value==null) return false;
            else if(value.trim().length()==0) return  false;
            else
                return  true;
        }
    }

    第三步声明约束注解(代码为增量形式,重复的就忽略了)

    @NotEmpty(message = "员工职位不能为空")
    private String job;

    第四步验证约束流程

    public static void main(String[] args) {
            Employee employee = new Employee();
            employee.setName("");
            employee.setId(18);
            employee.setJob("");
            Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
            for (ConstraintViolation constraintViolation:violations) {
                System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
                System.out.println("报错原因为:"+constraintViolation.getMessage());
            }
        }

    验证结果就不贴了,在自定义验证器中输出信息,发现确实调用了自定义的验证规则;我们并没有对@NotEmpty添加到Validator中,只是在实体类上声明了,就会找到这个注解,进而根据Constraint注解上的validatedBy,找到自定义的验证器,从而完成验证流程!

    Bean Validation内嵌的约束注解

    非空性验证两个: @NotNull  不为空   ; @Null  为空

    布尔型验证两个:@AssertTrue  布尔值为true ; @AssertFalse  布尔值不为空

    日期类型验证两个:@Past  日期必须为过去的日期 ; @Future  日期必须为未来某个日期

    正则表达式验证一个:@Pattern

    数值类型验证若干:@Min  String或Number类型大于等于该值  ; @Max  String或Number类型小于等于该值   这两种类型的值不支持小数校验

                              @DecimalMin  String或Number类型大于等于该值,支持小数 ;  @DecimalMax   String或Number类型小于等于该值,支持小数

    集合验证类型一个:@Size   String、Array、Collection、Map长度类型在设定范围内

    多值约束问题

    Bean Validation的一个特性:多值约束。

    @NotNull等内嵌注解最下面都会有@interface List这样一个注解,就是用来实现多值约束。 补充说明:一个注解无法再同一个位置标注两次,编译报错Duplicate Annotaion,所以就有了内部注解,此外还可以通过@Repeatable容器的概念来实现多个注解标注。

    image

    举个栗子,我们需要判断一个字符串包含多个子串,类似地一个数组、集合、map包含这种属性也是一样的;

    按照上面流程,定义约束注解  @Dictionary

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    @Constraint(validatedBy = {DictionaryValidator.class})
    public @interface Dictionary {
        String skillValue();
    
        String message() default "";
    
        Class<?>[] groups() default { };
    
        Class<? extends Payload>[] payload() default {};
    
        @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
        @Retention(RUNTIME)
        @Documented
        @interface List {
            Dictionary[] value();
        }
    }

    自定义约束验证器规则编写:

    public class DictionaryValidator implements ConstraintValidator<Dictionary,String> {
        String skillValue=null;
        @Override
        public void initialize(Dictionary constraintAnnotation) {
            skillValue=constraintAnnotation.skillValue();
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if(value==null){
                return false;
            } else if(value.trim().length()==0){
                return false;
            }else if(value.contains(skillValue)){
                return true;
            }
            return false;
        }
    }

    第三步声明注解,和@Repeatable有异曲同工之妙

     @Dictionary.List(value = {
                @Dictionary(message = "该员工不会java",skillValue = "java"),
                @Dictionary(message = "该员工不会vue",skillValue = "vue")
        })
        private String skill;

    第四步 验证约束

    public static void main(String[] args) {
            Employee employee = new Employee();
            employee.setName("lvbinbin");
            employee.setId(18);
            employee.setJob("");
            employee.setSkill("java");
            Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
            for (ConstraintViolation constraintViolation:violations) {
                System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
                System.out.println("报错原因为:"+constraintViolation.getMessage());
            }
        }

    得出结论:达到了我们的目的,校验skill属性包含 java vue子字符串,其实@Pattern编写正则表达式就可以达到这样的目的,但是集合 数组等类型多值判断时就需要多值校验了,还是有用武之处的!

           image

    组合约束

    假设现在校验员工身高,正常身高假设在1米以上3米以下,@Min(value=100) @Max(value=300)可能就完成这样一个简单的校验规则。Bean Validation新特性,组合校验。

    import static java.lang.annotation.ElementType.*;
    @NotNull(message = "如实禀报身高")
    @Min(message = "正常人身高在1米以上",value = 100)
    @Max(message = "正常人身高在3米以下",value = 300)
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) //注解应用的目标类型
    @Retention(RetentionPolicy.RUNTIME)                                 //注解应用时期
    @Documented
    @Constraint(validatedBy = {})
    public @interface Height {
        String message() default "";                                     //验证时输出信息
    
        Class<?>[] groups() default { };                                 //验证时所属的组别
    
        Class<? extends Payload>[] payload() default {};
    }

    声明注解@Height

        @Height
        private Integer height;

    校验流程

    public static void main(String[] args) {
            Employee employee = new Employee();
            employee.setName("lvbinbin");
            employee.setId(18);
            employee.setJob("");
            employee.setSkill("java vue");
            employee.setHeight(183);
            Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
            for (ConstraintViolation constraintViolation:violations) {
                System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
                System.out.println("报错原因为:"+constraintViolation.getMessage());
            }
        }

    补充记录:测试时候发现,@Height注解报错提示信息没有用,因为组合的注解没有通过,不是@Height注解校验不通过,可以再自定义约束验证器,这样校验不通过就能抛出@height中的message;

    Bean Validation验证规则流程

    调用 Validator.validate(beanInstance) 方法后,Bean Validation 会查找在 beanInstance上所有的约束声明(注解式),对每一个约束调用对应的约束验证器进行验证,由约束验证器的 isValid 方法产生,如果该方法返回 true,则约束验证成功,否则验证失败。验证失败生成约束违规对象(ConstraintViolation 的实例)并放到约束违规列表中。验证完成后所有的验证失败信息均能在该列表中查找并输出。

    条件:静态方法、字段无法约束验证;可以在类或者接口上使用约束验证,它将对该类或实现该接口的实例进行状态验证;

    级联验证方式

    对象之间存在级联关系,A类存在属性B,对A校验的同时,如果需要对B完成校验,在B上添加@Valid即可;

    举个栗子,假设Address存在属性Employee,在对Address实例校验时,在Employee上标注@Valid;

    @Setter
    @Getter
    public class Address {
        @Valid
        Employee employee;
        @NotNull
        String location;
    }

    校验流程

    public static void main(String[] args) {
            Employee employee = new Employee();
            employee.setName("lvbinbin");
            employee.setId(18);
            employee.setJob("");
            employee.setSkill("java vue");
            employee.setHeight(95);
            Address address = new Address();
            address.setEmployee(employee);
            Set<ConstraintViolation<Address>> violations = ValidationUtils.getValidator().validate(address);
            for (ConstraintViolation constraintViolation:violations) {
                System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
                System.out.println("报错原因为:"+constraintViolation.getMessage());
            }
        }

    输出结果:  注释掉@Valid,employee属性就不会校验

    image

    Bean Validation注解生效

    Bean Validation的注解可以在属性上 、在getter方法上生效,至于为什么在setter方法上没法生效 这个可能要成为谜题了;

    测试getter上生效

        private Date birth;
        public void setBirth(String date) throws ParseException {
            this.birth=new SimpleDateFormat("yyyy/MM/dd").parse(date);
        }
        @Past
        public Date getBirth(){
            return birth;
        }

    验证流程:

    public static void main(String[] args) throws ParseException {
            Employee employee = new Employee();
            employee.setName("lvbinbin");
            employee.setId(18);
            employee.setJob("");
            employee.setSkill("java vue");
            employee.setHeight(183);
            employee.setBirth("9102/12/31");
            Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
            for (ConstraintViolation constraintViolation:violations) {
                System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
                System.out.println("报错原因为:"+constraintViolation.getMessage());
            }
        }

    验证结果:

    image

    Bean Validation中提出了组的概念,这点和Jackson中的@JsonView很类似,我只展示我需要的属性;而Bean Validation中组,给每个校验属性分组,我只校验我指定的组中包含的属性,我不显示的指定组名,那就校验默认的组,Default组;

    使用说明一:默认不指定组的话,下面Validator.validate(beanInstance)和Validator.validate(beanInstance,Default.class)这两个输出是一样的,证明了默认组别叫Default; 这里的Default是javax.validation包中的,其他很多地方有这个类,不要到错了!

    @Setter
    @Getter
    public class User {
    
        @NotNull
        private String groupfieldA1;
        @NotNull
        private String groupfieldA2;
        @NotNull
        private String groupfieldB1;
        @NotNull
        private String groupfieldB2;
    
        public static void main(String[] args) {
            User user = new User();
            //Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user);
            Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, Default.class);
            for (ConstraintViolation constraintViolation:violations) {
                System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
                System.out.println("报错原因为:"+constraintViolation.getMessage());
            }
        }
    }

    使用说明二:

    组别一定要采用接口定义,组别不是接口会抛出异常:javax.validation.ValidationException: HV000045: A group has to be an interface 

    组的接口不一定要重新再定义,哪怕使用已有的接口也没有问题,我觉得只是作为标识来使用;

    image

        public static interface groupA{}
        @NotNull(groups = {groupA.class})
        private String groupfieldA1;

    这样通过Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, groupA.class)校验,就只会针对groupA组的属性校验,即属性groupfieldA1

    image

    使用说明三:

    组接口继承,属性也会继承被校验; 这点类比JsonView

        public static interface groupA{}
        public static interface groupB extends groupA{}
        @NotNull(groups = {groupA.class})
        private String groupfieldA1;
        @NotNull(groups = {groupB.class})
        private String groupfieldA2;

    这样通过Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, groupB.class)校验,组别B的属性校验,组别A的属性也会被校验;

    image

    组序列  @GroupSequence

    Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, groupA.class,groupB.class)支持多个组进行校验,而且组与组之间互不影响,校验组别顺序只与输入的组别顺序级groupA>groupB有关,组序列为了满足组与组之间互相存在影响而出现,保证了顺序以及避免不必要的校验(加入校验B依赖于校验A,校验A都没通过,校验B就没必要校验了)

    @Setter
    @Getter
    public class User {
        public static interface groupA{}
        public static interface groupB{}
    
        @GroupSequence(value = {groupA.class,groupB.class})
        public static interface group {}
    
        @NotNull(groups = {groupA.class})
        private String groupfieldA1;
        @NotNull(groups = {groupB.class})
        private String groupfieldA2;
        @NotNull
        private String groupfieldB1;
        @NotNull
        private String groupfieldB2;
    
        public static void main(String[] args) {
            User user = new User();
            //Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user);
            Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, group.class);
            for (ConstraintViolation constraintViolation:violations) {
                System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
                System.out.println("报错原因为:"+constraintViolation.getMessage());
            }
        }
    }

    输出截图: @GroupSequence保证了校验的顺序,以及存在依赖关系时的校验,第一个都没有通过,后续不再校验

    image

    Bean Validation接口规范

    MessageInterpolator接口:消息解析器,用来将验证过程中失败的消息以可读的方式传递给调用者; Bean Validation规范提供一个默认实现,configuration.getDefaultMessageInterpolator();

    用户自定义消息解析器只需要实现MessageInterpolator接口;

    Bean Validation 规范的输出消息默认从类路径下的 ValidationMessage.properties 文件中读取,用户也可以在约束注解声明的时候使用 message 属性指定消息内容。

    Configuration接口:收集上下文环境中的配置信息,主要用来计算如何给定正确的 ValidationProvider,并将其委派给 ValidatorFactory 对象。

    参考文档

    BeanValidation :  https://www.ibm.com/developerworks/cn/java/j-lo-beanvalid/

  • 相关阅读:
    LeetCode 169
    LeetCode 152
    LeetCode 238
    LeetCode 42
    LeetCode 11
    GDB基本调试
    小咪买东西(最大化平均值)
    codeforces 903D
    hdu 5883
    hdu 5874
  • 原文地址:https://www.cnblogs.com/lvbinbin2yujie/p/10601884.html
Copyright © 2011-2022 走看看