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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM