第三章、Bean Validation(约束声明和验证处理过程)

Bean Validation规范定义了一个框架,用于声明对JavaBean类,字段和属性的约束。

约束在类型上声明,并根据实例或示例图进行验证评估

3.1、对要验证的类要求

  • 要验证的对象必须满足以下要求:
    • 要验证的属性必须遵循JavaBeans规范定义的JavaBeans读取属性的方法签名约定。
    • 验证中不包括静态字段和静态方法。
    • 约束可以应用于接口和超类
  • 注解定义的目标可以是字段,属性或类型,条件是:
    • 约束定义支持指定的目标(java.lang.annotation.Target)
    • 在约束上声明的ConstraintValidators之一支持目标的声明类型(请参见第3.5.3节)

3.1.1、 对象验证

  • 约束声明可以应用于类或接口,将约束应用于类或接口可对类或实现接口的类的状态进行验证。

3.1.2、字段和属性验证

  • 约束声明可以应用于同一对象类型的字段和属性。但是,不应在字段及其关联属性之间重复相同的约束(约束验证将验证两次),建议持有约束声明的对象遵循单一状态访问策略(带注解的字段或属性)。
  • Java的持久化和Bean验证
    • 为了获得最大的移植性,承载Bean验证约束的持久属性应使用Java持久性中使用的相同访问策略。换句话说,将Bean Validation约束注解与Java 持久化注解放在同一元素(字段或getter)上。
    • 当使用约束声明对field进行注解时,将使用字段访问策略来访问通过这种约束验证的状态。
    • 当使用约束声明对property进行注解时,将使用字段访问策略来访问通过这种约束验证的状态。
    • 使用字段访问策略时,bean验证提供程序直接访问实例变量。使用属性访问策略时,bean验证提供程序通过属性访问器方法访问状态。当使用受约束的属性时,对于受约束的属性,要求该类遵循JavaBeans读取属性(由JavaBeans Introspector类定义)的方法签名约定。在这种情况下,对于类型T的每个约束属性,都有一个getter方法get。对于布尔属性,is是getter方法的替代名称。具体来说,如果getX是getter方法的名称。其中X是字符串,则持久属性的名称由java.beans.Introspector.decapitalize(X)的结果定义。
    • 字段或方法的可见性不受限制。不支持对非获取方法的约束

3.1.3、图形验证

  • 除了支持实例验证之外,还支持对象图的验证。图形验证的结果作为一组统一的约束违规返回。
  • 考虑bean X包含类型为Y的字段的情况,通过使用@Valid注解对字段Y进行注解,验证器将在验证X时验证Y(及其属性)。在运行时确定类型Y声明的字段(子类,实现)中包含的值的确切类型Z,使用Z的约束定义。这样可以确保标记为@Valid的关联具体正确的多态行为。
  • 集合值,数组值和通常Iterable的字段和属性也可以使用@Valid注解修饰。这将使迭代器的内容得到验证。支持任何实现java.lang.Iterable的对象。具体包括:
    • 数组对象
    • java.util.Collection
    • java.util.Set
    • java.util.List
    • java.util.Map(特殊对象,详见下文)
  • 验证迭代器提供的每个对象,对于Map,每个Map.Entry的值都经过验证(key未经验证)。
  • 像常规引用一样,它的类型在运行时确定,并且使用此特定类型的约束定义。
  • @Valid注解是递归应用的, 符合标准的实现可以避免按照3.5.1节中所述的规则进行无限循环。

3.2、约束声明

  • 约束声明主要通过注解放置在类或接口上。约束注解(请参阅第2.1节)可以应用于类型,任何类型的字段或任何与JavaBeans兼容的属性。
  • 在类上定义约束时,验证的类的实例将传递到ConstraintValidator进行验证,在字段上定义约束时,该字段的值将传递到ConstraintValidator.在getter上定义约束时,getter调用的结果将传递到ConstraintValidator.

3.3、继承(接口和父类)

  • 约束声明可以放在接口上。对于给定的类,由Bean验证提供程序评估保存在超类以及接口上的约束声明。规则在3.4.5节中正式描述
  • 约束声明的作用是累积的,根据Java语言规范可见性规则,将验证在超类getter上声明的约束以及在getter的覆盖版本上定义的所有约束。

3.4、组和组序列

  • 一组定义约束的子集。代替验证给定对象图的所有约束,仅验证子集,该子集由目标组定义。每个约束声明定义它所属的组的列表。如果未明确声明任何组,则约束属于默认组。

  • 组由接口表示:

  • 例子3.1、定义组

    • /**
      * 验证用户是否可计费的验证组
      */
      public interface Billable{}
      
      /**
      * 客户可以购买而无需任何麻烦的检查过程
      */
      public interface BuyInOneClick{}
      
  • 一个约束可以属于一个或多个组

  • 例子3.2、将组赋值给约束

    • /**
      * 用户信息
      */
      public class User{
        
        @NotNull
        private String firstname;
        
        @NotNull(groups=Default.class)
        private String lastname;
        
        @NotNull(groups={Billable.class, BuyInOneClick.class})
        private CreditCard defaultCreditCard;
      }
      
    • 在验证调用时,将验证一个或多个组,在对象图上评估属于该组集合的所有约束。在示例3.2中,当验证了Billable或BuyInOneClick组时,将在defaultCreditCard上选中@NotNull。当验证默认组时,将验证firstname和lastname上的@NotNull 。 温馨提醒:考虑对超类和接口的约束。

  • Default 组被预先定义在规范中

    • /**
      * 默认 Bean验证的默认组
      * 除非明确定义组列表,否则:
      * 约束属于默认组
      * 验证也应用默认组
      * 大部分结构约束应属于默认组
      */
      public interface Default{
        
      }
      

3.4.1、组的继承

  • 在某些情况下,组是一个或多个组的超集。可以通过Bean验证来描述。一个组可以通过接口继承来继承一个或多个组。

  • 例子3.3、组可以继承其他组

  • /**
    * 客户可以购买而无需任何麻烦的检查过程
    */
    public interface BuyInOneClick extends Default, Billable{}
    
  • 对于给定的接口Z,标记为属于组Z的约束(即,注解元素组包含接口Z的约束)或Z的任何父级接口(继承的组)都被视为组Z的一部分。

  • 例子3.4、使用继承组

    • public class User{
       
        @NotNull
        private String firstname;
        
        @NotNull(groups=Default.class)
        private String lastname;
        
        @NotNull(groups=Billable.class)
        private CreditCard defaultCreditCard;
      }
      
    • 如果验证组BuyInOneClick将导致如下约束检查:

      • @NotNull 在 firstname和lastname
      • @NotNull 在defaultCreditCard
    • 应该Default和Billable都是BuyInOneClick 父类接口

3.4.2、组的顺序

  • 默认情况下,不按任何特定顺序验证约束,无论它们属于哪个组,但是,在某些情况下控制约束验证的顺序很有用。在很多情况下,应该先验证一组初步的限制条件,然后再严重其他限制条件。这是两个示例:

    • 第二组依赖于稳定状态才能正常进行。第一组验证了此稳定状态。
    • 第二组可能消耗CPU或内存开销比较大,应尽可能避免对其进行验证(提前筛选)
  • 为了实现这种排序,可以将一个组定义为其他组的序列。当请求定义为序列的组时,必须按@GroupSequence.value定义的顺序依次处理组序列中的每个组。请注意,序列的一个组成员本身可以通过继承或序列定义由几个组组成。在这种情况下,每个组成的组必须遵循序列顺序。

  • 第3.5节中定义了处理组,如果序列中处理的组之一生成一个或多个约束违例,则不得处理序列中的后续组。这样可以确保仅在另一组约束有效情况下才验证下一组约束。

  • 定义序列的组和组成序列的组不得涉及到循环依赖性:

    • 间接或直接
    • 通过级联序列定义或组继承
  • 如果一个组包含发现一个循环依赖问题,那么抛出GroupDefinitionException异常

  • 定义序列的组不应该直接在约束声明中使用。换句话说,承载组序列的接口不应该在约束声明中使用。

  • 要将组定义为序列,必须使用@GroupSequence注解对接口进行标记。

    • /**
      * 定义一个组序列
      * 托管@GroupSequence的接口表示组序列。当托管一个类上时,代表该类的Default组。
      *
      * @author Emmanuel Bernard
      * @author Hardy Ferentschik
      */
      @Target({TYPE})
      @Retention(RUNTIME)
      public @interface GroupSequence{
        Class<?>[] value();
      }
      
  • 例子3.5、使用组序列

    • @ZipCodeCoherenceChecker(groups = Address.HighLevelCoherence.class)
      public class Address{
        
        @NotNull @Size(max=50)
        private String street1;
        
        @NotNull @ZipCode
        private String zipcode;
        
        @NotNull @Size(max =30)
        private String city;
        
         /**
        *  检查整个对象的一致性
        *  首先需要基本检查以确保绿色
        */
        public interface HighLevelCoherence{}
        
        /**
        * 检查基本约束和高级约束。
        * 如果基本约束失败,则不检查高级约束
        */
        @GroupSequence({Default.class, HighLevelCoherence.class})
        public interface Complete{}
      }
      
  • 在示例3.5中,当Address.Complete组得到验证时,属于Default组的所有约束都得到验证。如果其他任何一个失败,则验证将跳过HighLevelCoherence组。如果所有默认约束均通过,则验证HighLevelCoherence约束。

  • 注意事项:

    • 给定的约束可以属于按序列排序的两个或多个组,在这种情况下,将约束作为第一组的一部分进行评估,并在随后的组中将其忽略。有关更多信息,请参见第3.5节。

3.4.3、类重新定义默认组

  • 在示例3.5中,验证Default组不会验证HighLevelCoherence约束。为确保完成验证,用户必须使用完整组,这就破坏了您可能期望的某些封装。您可以通过重新定义Default组对于给定类的含义来解决此问题。要为类重新定义默认值,请在类上放置@GroupSequence注解;此序列表示将Default替换为此类的组的序列

  • 例子3.6、重新为Address定义默认组

    •  @GroupSequence({Address.class, HighLevelCoherence.class})
      @ZipCodeCoherenceChecker(groups = Address.HighLevelCoherence.class)
      public class Address{
        
        @NotNull @Size(max=50)
        private String street1;
        
        @NotNull @ZipCode
        private String zipcode;
        
        @NotNull @Size(max =30)
        private String city;
        
         /**
        *  检查整个对象的一致性
        *  首先需要基本检查以确保绿色
        */
        public interface HighLevelCoherence{}
      }
      
  • 在示例3.6中,当组Default验证地址对象时,将评估属于Default组并Address上所有的约束。如果验证成功了,则会评估地址中存在的所有HighLevelCoherence约束,换句话说,在验证地址的默认组时,将使用在地址类上定义的组顺序。

  • 由于序列不能具有循环依赖性,因此在序列的表明中使用“默认”不是一种选择,托管在类A上并属于Defaut组(默认或显式)的约束隐式属于组A。

  • 在类A上定义的序列(即重新定义该类的默认组)必须包含组A。换句话说。托管在类上的默认约束必须是序列定义的一部分。如果为类A重新定义Default组的@GroupSequence不包含组A,则在验证类或请求其元数据时会引发GroupDefinitionException。

3.4.4、隐式组

  • 可以在同一组中隐式地对几个约束进行分组,而不必在约束声明中显示列出此类组。接口Z上托管的每个约束和Default组的一部分(隐式或显式)都属于Z组,这对基于接口表示的角色来验证对象的部分状态很有用。

  • 例子3.7、接口和组托管约束

    • /**
      * 可审核对象合同
      */
      public interface Auditable{
        @NotNull String getCreationDate();
        
        @NotNull String getLastUpdate();
        
        @NotNull String getLastModifier();
        
        @NotNull String getLastReader();
      
      }
      
      
      public class Order implements Auditable{
        private String creationDate;
        
        private String lastUpdate;
        private String lastModifier;
       
        private String lastReader;
        
        private String orderNumber;
        
        public String getCreationDate(){
          return this.creationDate;
        }
        
        public String getLastUpdate(){
          return this.lastUpdate;
        }
        
        public String getLastModifier(){
          return this.lastModifier;
        }
        
        public String getLastReader(){
          return this.lastReader;
        }
        
        @NotNull @Size(min=10, max=10)
        public String getOrderNumber(){
          return this.orderNumber;
        }
      }
      
    • 在Defaul组上验证Order对象时,将验证以下约束:@NotNull (getCreateDate,getLastUpdate, getLastModifier, getLastReader,getOrderNumber); @Size(getOrderNumber) 它们都属于默认组。

    • 在Auditable组上验证Order对象时,将验证以下约束: getCreateDate, getLastUpdate, getLastModifier,getLastReader上的@NotNull。当请求组Auditable时,仅验证存在于Auditable(及其任何父类接口)上且属于Default组的默认组的约束,它使得调用者可以验证给定的对象可以安全地审计,即使对象状态本身无效。

3.4.5、正式组的定义

  • 定义组的正式规则如下。斜体文本是有关规则的注释

  • 对于每个类x:

    • A. 对于X的每个超类Y,组Y包含Y的组Y的所有约束

      • 该规则为递归发现奠定正式的概念
    • B. 组x包含以下约束:

      • 组x是在序列上使用的组,重新定义了类的默认组(请参阅第3.4.3节)
        1. 类X声明的每个约束都没有声明组,也没有明确声明组默认。
          • x类上所有约束
        2. x所实现的任何接口声明的所有约束,且未注释@GroupSequence,其中没有显示声明或显式声明组Default
          • 约束的接口上托管x的所有默认约束均由类层次结构的继承。接口标记@GroupSequence将被忽略。
        3. 如果X具有直接超类Y,则组Y中的每个约束。
          • x的超类上托管的所有默认约束:约束由类层次结构继承。
    • C. 如果x没有@GroupSequence注解,则组default包含以下约束:

      • 此规则定义在x上验证default时评估哪些约束

      • x的每一个约束

      • 如果x具有直接超类Y,则默认y组中的每个约束

        • 如果y重新定义默认组,则此规则是必须的
    • D. 如果X确实具有@GroupSequence注解,则组Default包含属于每个约束的每个约束@GroupSequence注解的声明的组。

      • 该规则描述了一个类如何为其自身重新定义组Default(请参见第3.4.3节)
        • @GroupSequence注解必须声明组X。
    • E. 对于每个接口z,组z包含接下来的约束:

      • 该规则定义了如何定义非默认组
      1. 接口z声明的每个约束,该约束未显式声明组或显式声明组Default
        • z上托管的所有默认约束:此规则正式定义每个接口的隐式分组(请参阅第3.4.4节)
      2. 由未注解接口z的@GroupSequence的任何超级接口声明的每个约束(未明确声明组)
        • 组z的接口上托管的所有默认约束都可以继承(请参阅第3.4.1节)
      3. 类x声明的每个约束,显式声明组z
        • 由x托管并标记为属于组z的每个约束
      4. x所实现的任何接口声明的所有约束,且未注解@GroupSequence,其中明确声明组Z
        • 由x的任何接口托管并标记为属于组z的每个约束
      5. 如果x具有直接超类y,则每个约束在y类的组z
        • 由x的任何超类托管并标记为属于z组的每个约束
    • F . 对于每个标有@GroupSequence的接口z,组z包含属于每个约束的每个约束。@GroupSequence注解声明的组。

      • 定义组序列的组成侧。但不定义序列的排序行为(请参阅第3.4.2节)
    • 当请求给定组G(由接口G表示),已验证类x时:

      • 评估属于组G的约束
      • 如果接口G没有注解@GroupSequence,则由G的超级接口表示的每个组都将重新要求验证
      • 如果接口G用了@GroupSequence注解,则由接口声明接口的表示的每个组需要@GroupSequence注解进行验证。
        • 声明给@GroupSequence的组的验证必须按照@GroupSequence声明的顺序进行,将排序顺序传播到组成已排序组的组(通过继承或组序列)
        • 如果组验证触发一个或多个约束的失败,则不得继续评估顺序中的组
      • 如果组G代表被@GroupSequence覆盖的x的默认组,则等效于操作当通过@GroupSequence覆盖给定x的default组时,其验证如下:
        • 请求@GroupSequence注解声明的接口表示的每个组进行验证。
        • 对@GroupSequence声明的组的验证必须按照@GroupSequence声明的排序顺序进行:将排序顺序传播到组成已排序的组(通过继承或组序列)
        • 如果组验证触发一个或多个约束的失败,则不得继续评估顺序中的组
    • 除非由@GroupSequence定义,否则评估顺序不受约束,特别是,可以通过同一边验证几个组,如果组定义导致组之间的循环排序顺序,则会引发GroupDefinitionException

    • 注意事项

      • 被顺序(直接或间接)执行的G组在其自身之前执行不被视为循环引用。

3.5、验证路由

  • 对于给定的组进行验证,应用于给定bean实例的验证例程应以不特定的顺序执行以下约束验证:
    • 对于所有可达的字段,除非在此验证程序期间已经处理了给定导航路径的验证程序(参见第3.5.1节),否则执行匹配目标组的所有字段级验证(包括超类上表示)匹配的所有字段级验证(包括在超类上)
    • 对于所有可达的getters,除非已经处理了给定导航路径的验证例程,否则执行匹配组的所有getter级别验证(包括在接口和次数上)匹配目标组(参见第3.5.1节)作为上一个组匹配的一部分。
    • 执行匹配目标组的所有类级验证(包括在接口和超类),除非在此验证程序期间已经处理了给定导航路径的验证程序(参见第3.5.1节)作为上一个组的一部分匹配。
    • 对于所有可访问和可级联的关联,执行所有级联验证(参见第3.5.1节),包括在接口和超类上表达的级联验证(参见第3.4.5节)
  • 可达的字段,getters方法和关联都作为级联被定义在3.5.2节)
  • 注意这就意味给定验证约束不会处理超过一次
  • 除非是组序列是顺序的,组可以验证没有特定顺序,这个实现验证路由可以为多个组运行相同例程。
  • 对象验证路由被描述如下,对于每个约束说明
    • 确定约束声明,使用适当的ContraintValidator(参见 第3.5.3)
    • 在适当的数据上执行约束验证实现的isValid方法
    • 如果isValid返回true,继续执行下一个约束
    • 如果isValid返回false,Bean验证提供统计ConstraintViolation对象去记录不符合验证规则违例(详情参见2.4节)

3.5.1、对象图验证

  • 在给定关联的@Valid注解(即对象引用或集合,数组,对象的迭代器),决定Bean Validator实现以递归地应用Bean验证例程(每个)关联对象的验证例程。此机制是递归:关联对象本身可以包含级联引用。

  • null引用将会被忽略

  • 为防止无限循环,如果在当前导航路径中已验证关联对象实例(从根对象开始),则Bean验证实现必须忽略级联操作。有关一个例子。请参阅3.8, 导航路径被定义为从根对象实例开始的一组@Valid关联,并到达关联的实例。给定的导航路径多次不能包含相同的实例(但是,虽然完整的验证的对象图)。有关示例,请参见示例3.8.

  • 注意事项;

    • 该对象图导航可以导致相同约束的多个验证和相同的对象实例,但是该组约束验证是确定性的,并且算法防止无限循环。
  • 例子3.8、对象图限制

  • # 假设对象验证图顺序如下
    Order -(lines)-> Orderline1
    Order -(lines)-> Orderline2 Orderline1 -(order)-> Order Orderline2 -(order)-> Order
    Order -(customer)-> User
    Order -(shippingAddress)-> Address1 Order -(billingAddress)-> Address2 Address1 -(inhabitant)-> User Address2 -(inhabitant)-> User
    User -(addresses)-> Address1 User -(addresses)-> Address2
    
    # 验证分支过程如下
    Order -(lines)-> Orderline1
    - order is ignored: Order is already present in the branch
    Order -(lines)-> Orderline2
    - order is ignored: Order is already present in the branch
    Order -(customer)-> User -(addresses)-> Address1
    - inhabitant is ignored: User is already present in the branch
    Order -(customer)-> User -(addresses)-> Address2
    - inhabitant is ignored: User is already present in the branch
    Order -(shippingAddress)-> Address1 -(inhabitant)-> User
    - addresses to Address1 is ignored: Address1 is already present in the branch
    Order -(shippingAddress)-> Address1 -(inhabitant)-> User -(addresses)-> Address2 - inhabitant is ignored: User is already present in the branch
    Order -(billingAddress)-> Address2 -(inhabitant)-> User
    - addresses to Address2 is ignored: Address2 is already present in the branch
    Order -(billingAddress)-> Address2 -(inhabitant)-> User -(addresses)-> Address1 - inhabitant is ignored: User is already present in the branch
    
  • 当找到相关对象上的失败约束时,构建ConstraintViolation对象,它们反映从根验证对象到达对象的路径(参见第4.2节)。

  • @Valid是一个正交的概念到组的概念,如果两个组依次执行,则第一组必须在评估第二组之前传递所有关联对象。但请注意,默认的组序列覆盖是本地定义类,并且它不会传播到关联对象,以下示例说明了:

  • 例子3.9 重新定义Driver类的默认组

    • @GroupSequence({Minimal.class, Driver.class})
      public class Driver{
        
        @Min(value=18, groups=Minimal.class)
        int age;
        
        @Valid
        Car car;
        
        // setter/getters
      }
      
  • 例子3.10 重新定义Car类的默认组

    • @GroupSequence({Car.class, Later.class})
      public class Car{
        @NotNull
        String type;
        
        @AssertTrue (groups = Later.class)
        Boolean roadWorthy;
        
        //setter/getters
      }
      
  • 例子3.11定义组序列

    • @GroupSequence({Minimal.class, Later.class})
      public interface SequencedGroups{}
      
  • 例子3.12 组序列重写不会传播到关联对象

    • Validator validator = Validation.buildDefaultValidatorFactory().getVaildator();
      
      Driver driver = new Driver();
      driver.setAge(16);
      Car porche = new Car();
      driver.setCar(porsche);
      
      Set<ConstraintViolation<Driver>> violations = validator.validate(driver);
      assert violations.size() == 2;
      violations = validator.validate(driver, SequencedGroups.class);
      assert violations.size() ==1;
      
    • 默认组序列为driver以及car重新定义。默认组通过validator.validate(driver)请求默认组(driver),则在类driver使用Minimal组进行验证。由于示例中的驾驶员年龄仅为16,因此该约束将失败。由于重新定义Driver序列,所以passedDrivingTest将不会评估,但是,还有约束违例,即@Notnull上的Car.type,原因是,组默认值会传播到Car(Minimal不会), 类Driver定义其自己的组序列,这意味着只会评估仅@Notnull标注type字段。

    • 在第二次调用验证,请求SequenceGroups组,其定义了Minimal、Later序列,在这种情况下,只有一次约束违规。再次@Min验证失败,但是这种情况下,Minimal组将传播到没有针对该组定义的任何约束的Car,属于该组的约束Later将不会被验证,直到属于Minimal通过的所有约束。

3.5.2、遍历属性

  • 在某些情况下,不应访问某些属性的状态。例如,如果Java持久化提供程序加载的属性是延迟属性或延迟关联,则访问其状态将触发来自数据库的负载。不期望的行为。

  • Bean 验证提供一种控制哪种属性可以且无法通过TraversableResolver.isReachable()访问。

  • 同样,尽管使用@Valid,但是仍然是级联验证的不希望的,Java持久性 例子2在flush操作不会级联关联到实体。您可以通过实现Traversable.isCascadable()方法。

  • /**
    *
    * 确定Bean验证提供程序是否可以访问属性,调用该类的方法,用于验证或级联的每个属性,可遍历的解析器实现必须是线程安全。
    *
    * @author Emmanuel Bernard
    */
    public interface TraversableResolver{
      
      /**
      *确定允许Bean验证提供程序是否达到属性状态
      *@param traversableObject 对象有traversableProperty,或如果validateValue被调用,这时为null
      *@param rootBeanType root的类型通过到Validator(验证)
      *@param pathToTraversableObject 路径是从root对象到traversableObject,
      *@param elementType  FIELD或METHOD
      *@return 如果Bean Validation提供允许去获取这个属性状态,返回true,否则返回false
      */
      boolean isReachable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elmentType);
      
      
      
       /**
      * 在bean实例上标记为@Valid是否允许级联验证
      * 注意只有isReachable调用返回true时,如果属性标记为@Valid,那么与isReachable是相同的参数,
      *@param traversableObject 对象有traversableProperty,或如果validateValue被调用,这时为null
      *@param rootBeanType root的类型通过到Validator(验证)
      *@param pathToTraversableObject 路径是从root对象到traversableObject,
      *@param elementType  FIELD或METHOD
      *@return 如果Bean Validation提供允许去级联验证,返回true,否则返回false
      */
      boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType);
    }
    
    
    
  • 调用isReachable可用于验证或级联访问的每个属性,如果此方法返回true,则可以访问属性。

  • isCascadable被调用所有关于级联的属性(即标记为@Valid),如果可访问它,则属性是cascadable ,如果isCascadable方法返回true。

  • 注意事项:

    • 只有isReachable调用返回true,才能调用isCascadable方法,换句话说,在调用isCasacadable之前调用isReacheable方法
  • traversableObject 就是代表对象实例将会被评估,如果检查是在validateValue调用中触发的,则返回null.

  • traversableProperty 是表示要遍历的traversableObject托管的属性的Node。属性的名称在3.1.2节中定义

  • rootBeanType 是正在验证(并传递到validate方法)的根的类。

  • pathToTraversableObject 表示从rootBeanType到traversableObject之间的Path(路径),如果根对象世俗traversableObject, pathToTraversableObject组成就是单个节点,同时名称为null,按照第4.2节(getPropertyPath)中描述的约定描述路径。

  • elementType 是放置注解的java.lang.annotation.ElementType。它可以是FIELD或METHOD,不应期望任何其他值。

  • Bean验证提供程序不得访问属性的状态,也不得在属性不可遍历时验证其约束,如果TraversableResolver为此属性返回true,则该属性是可遍历的。

  • 当面TraversableResolver调用时发现异常,那么这个异常被包装成ValidationException

  • 下面的示例假定示例3.13中定义的对象图,并假定将验证操作应用于地址对象。

  • 例子3.13 定义一个Country

    • public class Country {
      
          @NotNull
          private String name;
      
          @Size(max = 2)
          private String ISO2Code;
      
          @Size(max = 3)
          private String ISO3Code;
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getISO2Code() {
              return ISO2Code;
          }
      
          public void setISO2Code(String ISO2Code) {
              this.ISO2Code = ISO2Code;
          }
      
          public String getISO3Code() {
              return ISO3Code;
          }
      
          public void setISO3Code(String ISO3Code) {
              this.ISO3Code = ISO3Code;
          }
      }
      
    • public class Address{
        
        @NotNull @Size(max=30)
        private String addressline1;
        
        @Size(max=30)
        private String addressline2;
        
        @Size(max=11)
        private String zipCode;
        
        @Valid
        private Country country;
        
        private String city;
        
        public String getAddressline1() {
              return addressline1;
          }
      
          public void setAddressline1(String addressline1) {
              this.addressline1 = addressline1;
          }
      
          public String getAddressline2() {
              return addressline2;
          }
      
          public void setAddressline2(String addressline2) {
              this.addressline2 = addressline2;
          }
      
          public String getZipCode() {
              return zipCode;
          }
      
          public void setZipCode(String zipCode) {
              this.zipCode = zipCode;
          }
        @Size(max=30) @NotNull
        public String getCity(){
          return city;
        }
        
        public void setCity(String city){
          this.city;
        }
        
        public Country getCountry(){
          return country;
        }
        public void setCountry(Country country){
          this.country = country;
        }
      }
      
  • 当Bean 验证提供者将要验证ISO3Code字段的约束,通过下列参数值它调用TraversableResolver.isReachable()的实例确保ISO3Code字段属性是可达的:

    • traversableObject : country . 这个实例将会返回address.getCounry()
    • traversableProperty: 一个节点名称为ISO3Code, traversableObject对应这个属性名称将会被验证。
    • rootBeanType : Address.class 被验证的根对象
    • pathtoTraversableObject: 这个路径包含单个名称为“country"的节点,这个路径是从address到country实例
    • elementType: ElementType.FIELD. ISO3Code属性将被注解这个字段
  • 当Bean验证提供者提供将要级联验证country(Address 对象),它将会调用TraversableResolver.isReachable()返回true,实例确保country属性是可达的。它采用如下参数调用TraversableResolver.isCascadable()方法:

    • traversableObject: address. address实例
    • traversableProperty: 节点名称为country, traverableObject对应这个名称的属性将会被验证
    • rootBeanType: Address.class 被验证的根对象
    • pathtoTraversableObject: 这个路径包含单个节点,节点名称为null
    • elementType:ElementType.FIELD 这个country属性将会被注解到。
  • 例子 3.14、 java 持久化关心TraversableResolver

    • public class JPATraversableResolver implements TraversableResolver{
        
        public boolean isReachable(Object traversableObject, Path.Node traversableProprty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType){
          
          return traversableObject == null || 
            		 Persistence.getPersistenceUtil().isLoaded(traversableObject, traversableProperty.getName());
        }
        
        public boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType){
          return true;
        }
      }
      
    • Bean验证中默认使用的可遍历解析器的行为如下:

      • 如果Java持久性在运行时环境中可用,则如果Java持久性将该属性视为已加载,则认为该属性可访问,一个典型的实现将使用Persistence.getPersistenceUtil().isLoaded(Object,String) 来实现这种协定
      • 如果JavaPersistence 在运行时环境中不可用,则认为所有属性都可以访问的。
      • 所有属性都考虑级联
    • 请参阅第4.4节以了解如何传递自定义TraversableResolver

3.5.3、ConstraintValidator 解析算法

  • 约束与一个或多个ConstraintValidator实现相关联,每个ConstraintValidator<A,T>接受类型T,执行的ConstraintValidator取决于托管约束的类型。对于给定的约束评估,应考虑单个ConstraintValidator.

  • 如果约束声明托管在类或接口上,则目标类型为类或接口。如果约束托管在类属性上,则属性的类型为目标类型,如果约束托管在getter上,则getter的返回类型为目标类型。换句话说。解析算法认为类型是方法签名中定义的类型,而不是值的运行时类型。

  • 下面编写的规则正式描述了以下语句:选择来验证声明的类型T的ConstraintValidator是这样的一种。其中ConstraintValidator支持的类型是T的超类型,并且没有其他ConstraintValidator支持的类型是T的超类型和不是所选ConstraintValidator支持类型的超类型。

  • 当约束A置于目标声明的类型T,接下来解析规则应用如下:

    • 基本数据类型等同于其包装类,因此基本数据的数组类型等同于包装类型的数组

    • 如果T是U的子类型,则说ConstraintValidator<A,U>符合T(根据Java语言规范第3版第4.10章子类型[1])。请注意,如果T=U, 则T是U的子类型。

    • 如果在约束A列出的ConstraintValidator中未找到符合T的ConstraintValidator,则引发UnexceptedTypeException。

    • 如果U 是 V的子类,那么从考虑严格性一个ConstraintValidator<A,U>比一个ConstraintValidator<A,V>更具体,如果U是V的子类且U !=V,我们称为U是V严格子类(根据Java语言规范第3版第4.10章子类型[2])

    • 如果没有其他符合T的ConstraintValidator<A,V>严格比ConstraintValidator<A,U>更具体,则认为符合T的ConstraintValidator<A,U>是最具体的。

    • 如果有多个最具体的ConstraintValidator,那么UnexceptedTypeException将会抛出

    • 注意事项:

      • 虽然Java编译器本身无法确定约束声明是否会导致UnexpectedConstraint声明和验证过程应用这些规则,并避免在模棱两可的情况下进行编译,该规范鼓励Bean验证提供者向其用户提供这种工具。
    • 让我们来看几个声明及其各自的ConstraintValidator分别率。假设如3.15所示:

    • 例子3.15 ConstraintValidator 和类型解析

      • [...]
        @Constraint(validatedBy={
          SizeValidatorForCollection.class,
          SizeValidatorForSet.class,
          SizeValidatorSerializable.class
        })
        public @interface Size{...}
        
        public class SizeValidatorForCollection implements ConstraintValidator<Size, Collection>{...}
        
        public class SizeValidatorForSet implements ConstraintValidator<Size,Set>{...}
        
        public class SizeValidatorForSerializable implements ConstraintValidator<Size, Serializable>{...}
        
        public interface SerializableCollection extends Serializable, Collection {}
        
        
      • 这个解析展示如下表格3.1

      • 声明解析
        @Size Collection getAddresses(){…}直接匹配 SizeValidatorForCollection
        @Size Collection<?> getAddresses(){…}SizeValidatorForCollecation : Collection是 Collection<?>的父类
        @Size Collection
        getAddresses(){…}
        SizeValidatorForCollection: Collection是Collection
        的父类
        @Size Set
        getAddresses(){…}
        SizeValidatorForSet: Set是Set
        父类
        @Size SortedSet
        getAddresses(){…}
        SizeValidatorForSet: Set 是SortedSet
        的父类
        @Size SerializableCollection getAddresses(){…}UnexpectedTypeException抛出:因为SerializableCollection 的父类既有Collection和Serializable,且Collection 和Serializable无父子关系。
        @Size String getName(){…}UnexpectedTypeException抛出:因为没有ConstraintValidator类没有是String的父类处理器

3.6、例子

  • 第一个示例演示了如何注解bean,字段和getter以表达一些约束

    • @ZipCodeCityCoherenceChecker
      public class Address{
        
        @NotNull @Size(max=30)
        private String addressline1;
        
        @Size(max=30)
        private String addressline2;
        
        @Size(max=11)
        private String zipCode;
        
       
        private String city;
        
        public String getAddressline1() {
              return addressline1;
          }
      
          public void setAddressline1(String addressline1) {
              this.addressline1 = addressline1;
          }
      
          public String getAddressline2() {
              return addressline2;
          }
      
          public void setAddressline2(String addressline2) {
              this.addressline2 = addressline2;
          }
      
          public String getZipCode() {
              return zipCode;
          }
      
          public void setZipCode(String zipCode) {
              this.zipCode = zipCode;
          }
        @Size(max=30) @NotNull
        public String getCity(){
          return city;
        }
        
        public void setCity(String city){
          this.city;
        }
      }
      
    • 在对Address对象执行验证例程期间

      • addressline1字段值传递给@NotNull 以及@Size约束验证实现。
      • addressline2字段值传递给@Size约束验证实现
      • getCity 值传递给@Size和@NotNull 约束验证实现
      • @ZipCodeCoherenceChecker 的约束验证实现了传递Address对象参数的isValid方法
  • 第二例子展示对象图验证 3.17

    • public class Country {
      
          @NotNull
          private String name;
      
          @Size(max = 2)
          private String ISO2Code;
      
          @Size(max = 3)
          private String ISO3Code;
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getISO2Code() {
              return ISO2Code;
          }
      
          public void setISO2Code(String ISO2Code) {
              this.ISO2Code = ISO2Code;
          }
      
          public String getISO3Code() {
              return ISO3Code;
          }
      
          public void setISO3Code(String ISO3Code) {
              this.ISO3Code = ISO3Code;
          }
      }
      
      public class Address{
        
        @NotNull @Size(max=30)
        private String addressline1;
        
        @Size(max=30)
        private String addressline2;
        
        @Size(max=11)
        private String zipCode;
        
        @NotNull @Valid
        private Country country;
        
        private String city;
        
        public String getAddressline1() {
              return addressline1;
          }
      
          public void setAddressline1(String addressline1) {
              this.addressline1 = addressline1;
          }
      
          public String getAddressline2() {
              return addressline2;
          }
      
          public void setAddressline2(String addressline2) {
              this.addressline2 = addressline2;
          }
      
          public String getZipCode() {
              return zipCode;
          }
      
          public void setZipCode(String zipCode) {
              this.zipCode = zipCode;
          }
        @Size(max=30) @NotNull
        public String getCity(){
          return city;
        }
        
        public void setCity(String city){
          this.city;
        }
        
        public Country getCountry(){
          return country;
        }
        public void setCountry(Country country){
          this.country = country;
        }
      }
      
      
    • 在对Address对象执行验证例程期间,将处理对addressLine1, addressLine2, zipCode, getCity和country的约束,以及对country对象本身验证,尤其是对country.name进行@NotNull, ISO2Code和ISO3Code检查@Size

    • 假设@NotEmty定义如下:

    • @Documented
      @NotNull
      @Size(min=1)
      @ReportAsSingleViolation
      @Constraint(validatedBy =NotEmpty.NotEmptyValidator.class)
      @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
      @Retention(RUNTIME)
      public @interface NotEmpty{
        String message() default "{com.acme.constraint.NotEmpty.message}";
        Class<?> groups() default {};
        Class<? extends Payload>[] payload() default{};
        
        @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
        @Retention(RUNTIME)
        @Documented
        @interface List{
          NotEmpty[] value();
        }
        
        class NotEmptyValidator implements ConstraintValidator<NotEmpty, String> {
          public void initialize(NotEmpty constraintAnnotation){}
          
          public boolean isValid(String value, ConstraintValidatorContext context){
            return true;
          }
        }
      }
      
  • 第三个例子 展示超类、继承和组合约束

    • public interface Person{
        
        @NotEmpty
        String getFirstName();
        
        String getMiddleName();
        
        @NotEmpty
        String getLastName();
      }
      
      
      public class Customer implements Person{
        
        private String firstName;
        
        private String middleName;
        
        private String lastName;
        
        @NotNull
        private String customerId;
        
        @Password(robustness=5)
        private String password;
        
        public String getFirstName(){
          return firstName;
        }
        
        public void setFirstName(String firstName){
          this.firstName = firstName;
        }
        
        public String getMiddleName(){
          return middleName;
        }
        
        public String setMiddleName(String middleName){
          this.middleName = middleName;
        }
        
        public String getLastName(){
          return lastName;
        }
        
        public void setLastName(String lastName){
          this.lastName = lastName;
        }
        
        public String getCustomerId(){
          return customerId;
        }
        public void setCustomerId(String customerId){
          this.customerId = customerId;
        }
        
        public String getPassword(){
          return password;
        }
        
        public void setPassword(String password){
          this.password = password;
        }
      }
      
      public class PreferredGuest extends Customer{
        
        @CreditCard
        private String guestCreditCardNumber;
        
        public String getGuestCreditCardNumber(){
          return guestCreditCardNumber;
        }
        public void setGuestCreditCardNumber(String guestCreditCardNumber){
          this.guestCreditCardNumber = guestCreditCardNumber;
        }
      }
      
      public class CommonGuest extends customer{}
      
    • 当验证PreferredGuest,如下约束将会被验证

      • @NotEmpty, @NotNull 和@Size(min=1)标记到firstName
      • @NotEmpty, @NotNull和@Size(min=1)标记到lastName
      • @NotNull 标记在customerId, @Password 标记在password
      • @CreditCard 标记在guestCreditCardNumber
    • 当验证CommonGuest, 如下约束将会被验证

      • @NotEmpty, @NotNull 和@Size(min=1)标记到firstName
      • @NotEmpty, @NotNull和@Size(min=1)标记到lastName
      • @NotNull 标记在customerId, @Password 标记在password
  • 第四个例子,3.19,展示组序列的影响

    • @GroupSequence({First.class, Second.class, Last.class})
      public interface Complete{}
      
      public class Book{
        
        @NotEmpty(groups=First.class)
        private String title;
        
        @Size(max=30, groups=Second.class)
        private String subtitle;
          
        @Valid
        @NotNull(groups=First.class)
        private Author author;
        
        public String getTitle(){
          return title;
        }
        
        public void setTitle(String title){
          this.title=title;
        }
        
        public String getSubtitle(){
          return subtitle;
        }
        public void setSubtitle(String subtitle){
          this.subtitle = subtitle;
        }
        public Author  getAuthor(){
          return author;
        }
        
        public void setAuthor(Author author){
          this.author = author;
        }
      }
      
      public class Author{
        
        @NotEmpty(groups=Last.class)
        private String firstName;
        
        @NotEmpty(groups=First.class)
        private String lastName;
        
        @Size(max=30, groups=Last.class)
        private String company;
        
        public String getFirstName(){
          return firstName;
        }
        
        public void setFirstName(String firstName){
          this.firstName = firstName;
        }
        
        public String getLastName(){
          return lastName;
        }
        public void setLastName(String lastName){
          this.lastName = lastName;
        }
        
        public String getCompany(){
          return company;
        }
        public void setCompany(String company){
          this.company = company;
        }
      }
      
    • 假设验证Complete组使用如下book实例

    • Author author = new Author();
      author.setLastName("Baudelaire");
      author.setFirstName("");
      Book book = new Book();
      book.setAuthor(author);
      
    • 这个验证路由将会返回失败:

      • @NotNull 失败(来自于@NotEmpty)标记在title字段上

      • 作为title和author.lastname都被检查为First 组,如果实例更新为

      • book.setTitle("les fleurs du mal");
        author.setCompany("Some random publisher with a very very very long name");
        
    • 这个验证路由将会返回失败:

      • author.firstName 失败 通过 @Size(min=1)(来自@NotEmpty)约束
      • author.company 失败 通过@Size约束
    • 也就是说验证First和Second 组时没有任何错误,Last 组将会验证失败

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值