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/

转载于:https://www.cnblogs.com/lvbinbin2yujie/p/10601884.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值