第四章、Validation API使用

Bean 验证的默认API的包名是javax.validation

4.1、验证器的API

  • 主要Bean验证的API是javax.validation.Validator 接口

  • Validator实例能够验证bean及其关联对象(如果有)的实例。推荐将Validator实例的缓存留给ValidatorFactory。验证程序的实现是线程安全的。

  • /**
    * bean实例的验证, 实现接口必须是线程安全
    *
    * @author Emmanuel Bernard
    * @author Hardy Ferentschik
    */
    public interface Validator {
      
      /**
      * 在这个对象验证所有的约束
      *@param object 验证的对象
      *@param groups 验证目标的组或组列表
      *@return  返回约束违例或Set是空集合
      *@throws IllegalArgumentException 如果对象是null或如果null参数传递给varargs 组
      *@throws ValidationException 如果在验证过程中发生不可恢复的错误
      */
      <T> Set<ConstraintViolation<T>> validate(T object, Class<?> ... groups);
      
      
      /**
      * 验证放置在名为propertyName的object的属性上的所有约束。
      *@param object 验证的对象
      *@param propertyName 要验证的属性(例如字段和getter约束)
      *@param groups 验证目标的组或列表(默认是javax.validation.groups.Default)
      *@return 返回约束违例或空集合(如果没有)
       *@throws IllegalArgumentException 如果对象是null或如果null参数传递给varargs 组
      *@throws ValidationException 如果在验证过程中发生不可恢复的错误
      */
      <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?> ... groups);
      
      
      /**
      * 如果属性值是value的ConstraintViolation, 则验证放置在类beanType的名为propertyName的属性上的所有约束,对象为ConstraintViolation#getRootBean() 和
      *ConstraintViolation#getLeafBean() 返回null
      *@param beanType bean的类型
      *@param propertyName 验证的属性
      *@param value 验证属性的值
      *@param groups 验证目标的组或列表(默认是javax.validation.groups.Default)
      *@return 返回约束违例或空集合(如果没有)
      *throws IllegalArgumentException 如果对象是null或如果null参数传递给varargs组
      *@throws ValidationException 如果在验证过程中发生不可恢复的错误
      */
      <T> Set<ConstraintViolation <T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups);
      
      
      /**
      * 返回描述bean约束的描述对象。
      * 返回的对象(以及相关的对象,包括ConstraintDescriptor 是不可变的
      *@param clazz  类或接口类型被评估
      *@return 指定类的bean描述符
      *@throws IllegalArgumentException 如果clazz 为null
      *@throws ValidationException 如果在验证过程中发生不可恢复的错误
    	*
    	*/
      BeanDescriptor getConstraintsForClass(Class<?> clazz);
      
      
      /**
      * 返回指定类型的实例,以允许访问提供程序特定的API。如果Bean验证提供程序实现不支持指定的类,则抛出ValidationException
      * @param type 要返回的对象的类
      * @return 指定类的实例
      * @throws ValidationException 如果提供者不支持的调用
      *
      */
      public <T> T unwrap(Class<T> type);
    }
    
  • 第5章介绍了getConstraintsForClass.

  • 提供unwrap的一种方式来访问特定于Bean验证提供程序的给定类型的对象,通常是对验证者的补充,使用此方法会使您的代码不可移植。

  • 示例4.1、使用unwrap访问提供者特定的合约

  • // 如果使用ACME提供者
    ACMEValidator acmeValidator = factory.unwrap(ACMEValidator.class);
    acmeValidator.setSpecificConfiguration(...);
    

4.1.1、验证方法

  • Set<ConstraintViolation> validate(T object, Class<?> … groups)用于验证给定的对象。此方法实现了第3.5节中描述的逻辑。返回包含表示失败约束的所有ConstraintViolation对象的Set集合,返回返回空Set集合
  • Set<ConstraintViolation > validateProperty(T object, String propertyName, Class<?> … goups) 验证对象的给定字段或属性。该属性名称是JavaBeans属性名称(由JavaBeans Introspector类定义)。此方法实现第3.5节中描述的逻辑,但仅实现给定的属性。此方法不支持@Valid。此方法对于部分对象验证很有用。
  • Set<ConstraintViolation> validateValue(Class beanType, String propertyName, Object value, Class<?> … groups) 验证 bean 类型或其任何超类上存在的propertyName引用的属性,如果属性的值就是给定的value, 此方法不支持@Valid。此方法对于提前验证(即在填充或更新JavaBean之前)很有用。
  • 注意:
    • 如果多个受约束的字段或获取器共享相同的名称,并且根据Java可见性规则在类层次结构中彼此隐藏,则未指定评估的约束列表。在本规范的更高版本中将对此进行澄清。请注意,方法覆盖不会受到影响。
    • 如果getter和字段具有相同的名称,并且存在于层次结构的不同级别,则未指定评估的约束列表。在本规范的更高版本中将对此进行澄清。
    • 但是,始终评估在最特定(层次结构)元素类型上托管的约束。
  • 如果在验证期间发生一些不可恢复的故障,则会引发ValidationException. 在某些情况下(无效的组定义,无效的约束定义,无效的约束声明),可以专门处理此异常。有关更多信息,请参见第8章或相关章节。
4.1.1.1、例子
  • 所有示例将基于以下类定义,约束声明和地址实例。

  • public class Address{
      
      @NotNull @Size(max=30)
      private String addressline1;
      
      @Size(max=30)
      private String addressline2;
      
     
      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 = city;
      }
      
    }
    
    Address address = new Address();
    address.setAddressline1(null);
    address.setAddressline2(null);
    address.setCity("Llanfairpwllgwyngyllgogerychwyrndrobwyll-llantysiliogogogoch");
    
  • 下面代码将会返回两个ConstraintViolation对象。一个对象是addressline1不满足@NotNull 和一个对象city不满足@Size

    • validator.validate(address).size() == 2
      
  • 下面代码将会返回一个ConstraintViolation对象,city字段不满足@Size, 仅仅验证了city字段

    • validator.validateProperty(address, "city").size() == 1
      
  • 下面代码不会返回ConstraintViolation对象,因为"Paris"的值并不匹配city的值

    • validator.validateValue("city", "Paris").size() == 0
      

4.1.2、groups(组)

  • 通过组,您可以限制在验证期间应用的约束集,目标组作为参数传递给validate, validateProperty和validateValue方法,在第3.5节中将应用属于目标组的所有约束,如果未传递任何组,则假定为Default,第2.1.1.2节介绍了如何在约束条件下定义组。当评估一组以上并传递给各种验证方法时,顺序不受限制。这等效于对组G的验证,该组G继承了传递给验证方法的所有组(即,实现所有接口)
4.1.2.1、例子
  • // 验证一个最小set集合的约束
    public interface Minimal{}
    
    public class Address{
      
      @NotEmpty(groups = Minimal.class)
      @Size(max=50)
      private String street1;
      
      @NotEmpty
      private String city;
      
      @NotEmpty(groups={Minimal.class, Default.class})
      private String zipCode;
      
      ...
    }
    
  • 在上面例子中,street1的@NotEmpty属于Minimal.class组和@Size属于默认组, city的@NotEmpty属于默认组, zipCode的@NotEmpty属于Minimal.class 和Default.class组。

  • // 默认是default组,也就是处理street1的@Size(max=50),city上@NotEmpty, zipCode@NotEmpty的校验
    validator.validate(address);
    
  • // 校验组是Minimal.class, 也就是处理,street1的@NotEmpty, zipcode的@NotEmpty
    validator.validate(address, Minimal.class);
    
  • // 所有注解都会被校验和执行, 假设zipCode为空,只有一个ConstraintViolation对象代表失败且非空校验验证将只会执行一次
    validator.validator(address, Minimal.class, Default.class)
    
  • 让我们来看一个更加负责组序列例子

    • public class Address{
        
        @NotEmpty(groups=Minimal.class)
        @Size(max=50, groups=FirstStep.class)
        private String street1;
        
        @NotEmpty(groups=SecondStep.class)
        private String city;
        
        @NotEmpty(groups={Minimal.class, SecondStep.class})
        private String zipCode;
        ...
        public interface FirstStep{}
        
        public interface SecondSetp{}
        
        @GroupSequence({FirstStep.class, SecondStep.class})
        public interface Total{}
      }
      
    • 当运行如下验证代码

    • validator.validate(address, Minimal.class, Total.class);
      
      // 这个验证将会处理 street1的@NotEmpty 和 @Size,以及zipCode的@NotEmpty, 如果street1的@Size不会产生错误,那么city的@NotEmpty将会被处理,同时,zipCode的@NotEmpty将不会处理,因为它之间已经处理过了。
      
    • 当运行如下验证代码

    • validator.validate(address, Total.class, SecondStep.class);
      
      // 即使street1的@Size验证失败,那么city的@NotEmpty和zipCode的@NotEmpty也会执行,因为后面加一个SecondStep.class组
      
    • 注意: 如果组定义是无效,那么GroupDefinitionException将抛出异常

4.2、ConstraintViolation

  • ConstraintViolation一个描述单个约束失败的类,验证一个对象将会返回ConstraintViolation的集合

  • /**
    *
    *描述一个约束违规,这个对象展示约束违规上下文信息,也作为描述这个约束违规的信息
    *
    * @author Emmanuel Bernard
    */
    public interface ConstraintViolation<T> {
      
      /**
      *@return 针对此约束违例的内插错误消息。
    	*/
      String getMessage();
      
      /**
      *
      *@return 针对此约束违例的非内插错误消息
      */
      String getMessageTemplate();
      
      /**
      *
      *@return  root bean 将被验证
      */
      T getRootBean();
      
      /**
      *
      *@return 验证root bean的类的类型
    	*/
      Class<T> getRootBeanClass();
      
      /**
      * 如果是bean的约束,则将约束应用于该bean的实例上,如果是一个属性的约束,则将托管应用于该bean的实例属性上。
    	* @return 返回应用于叶子的bean
    	*/
      Object getLeafBean();
      
      /**
      *
      *@return 从rootBean到该值的一个属性路径
      */
      Path getPropertyPath();
      
      /**
      *
    	*@return 值未通过约束。
    	*/
      Object getInvalidValue();
      
      /**
      * 约束元数据报告失败
      * 返回实例不可变的
      *@return 约束元数据
      */
      ConstraintDescriptor<?> getConstraintDescriptor();
      
    }
    
  • 对于失败约束getMessage方法返回内部(本地)消息(详情查看4.3节),这个可以被用来向客户端暴露友好的提示信息

  • getMessageTemplate方法返回一个非内部错误消息(通常是在约束声明一个message属性),框架可以使用这个作为错误代码的key

  • getRootBean方法返回已经被验证的根对象,它导致失败的约束(例如Validator.validate方法的入参的对象)

  • getInvalidValue方法返回值(字段、属性或验证对象)作为参数传递给isValid的方法。

  • getConstraintDescriptor提供获取失败约束元数据(查看5.5节)

  • getPropertyPath返回这个Path对象,它代表从根对象到失败对象之间导航路径

  • 
    /**
    * 表示从一个对象到另一个对象的导航路径,也就对象图
    * 每个路径的元素都是由Node表示
    * 路径就是Iterator有序迭代出的节点序列
    * @author Emmanuel Bernard
    */
    public interface Path extends Iterable<Path.Node>{
      /**
      * 表示导航路径的元素
      *
      */
      interface Node{
        /**
        * 节点属性的名称,如果表示一个叶子节点,这个name为null,(特别地,表示path的根节点的名称可以是null)
        *
    		*/
        String getName();
        
        /**
        *@return true 如果节点对象被包含在Iterable或在map
    		*
    		*/
        boolean isInIterable();
        
        /**
        *@return 在一个数组或List中使用索引表示节点的位置,其他数据类型返回null
        *
        */
        Integer getIndex();
        
        /**
        *
    		*@return 在如果节点被map包含,那么这个方法返回节点的对应map的key,其他数据结构,这个方法返回null;
    		*/
        Object getKey();
      }
      
    }
    
  • 路径是用节点组成,它构建规则如下:

    • 如果失败对象是根对象,那么一个节点名称设置为null,然后添加到路径中
    • 当如下关联数据结构被遍历
      • 如果一个节点对象的名称等于关联的属性(字段名称或Java Bean属性名称)将被添加路径中。
      • 如果关联是List或数组,那么接下来节点对象在在getIndex中包含索引值
      • 如果关联是Map,那么接下来节点对象表示一个map键值对,这个key可以通过getKey获取
      • 对应所有Iterable或Map, 接下来节点对象被标记做inIterable(isInIterable)
    • 对于属性级别约束(字段或getter方法)
      • 等于属性(属性名称或Java Bean 属性名)名称的节点对象被添加路径(Path)中
      • 这个属性路径将考虑已完成
    • 对于类级别约束:
      • 一个节点对象添加到Path,它的名称为null
      • 这个属性路径考虑完成
    • 注意:
      • 一个给定节点节点可以从先前关联遍历中衍生出inIterable,key和index属性
    • 注意:
      • 从rootBean和propertyPath, 它有可能会重建失败上下文信息。
  • 假设如下对象的定义,当book对象被验证:

    • @SecurityChecking
      public class Author{
        private String firstName;
        
        @NotEmpty(message="lastname must not be null")
        private String lastName;
        
        @Size(max=30)
        private String company;
        ...
      }
      
      @AvailableInStore(groups={Availability.class})
      public class Book{
        
        @NotEmpty(groups={FirstLevelCheck.class, Default.class})
        private String title;
        
        @Valid
        @NotNull
        private List<Author> authors;
        
        @Valid
        private Map<String, Review> reviewsPerSource;
        
        @Valid
        private Review pickedReview;
        ...
      }
      public class Review{
        @Min(0)
        private int rating;
        ...
      }
      
    • 属性路径评估将如下展示:表4.1

    • 表4.1、propertyPath 例子

      • 约束属性路径(propertyPath)
        Book上的@AvailableInStoreNode(name=null, inIterable=false, index=null, key=null)
        Book.title的@NotEmptyNode(name=title, inIterable=false, index=null, key=null)
        Book.authors的@NotNullNode(name=authors,inInterable=false, index=null, key=null)
        第四个author对象的@SecurityCheckingNode(name=authors, inInterable=false, index=null, key=null)
        Node(name=null, inIterable=true, index=3, key=null)
        第四个Author.company(原文是Author.lastName,这个地方应该有问题)的@SizeNode(name=authors, inIterable=false, index=null, key=null)
        Node(name=company,inIterable=true, index=3, key=null)
        Author.lastName第1个author对象的@NotEmptyNode(name=authors, inIterable=false, index =null, key=null)
        Node(name=lastName,inIterable=true,index=0,key=null)
        关联key为Consumer Report Map的Reviw.rating的@MinNode(name=reviewsPerSource,inIterable=false, index=null, key=null)
        Node(name=rating, inItreable=true, index=null, key=“Consumer Report”)
        Review.rating的关联picked review的@MinNode(name=pickedReview, inIterable=false, index=null,key=null)
        Node(name=rating,inIterable=false, index=null, key=null)

        注意:

        • Bean验证对象的实现必须确保一个ConstraintViolation实现Serializable接口,并且提供路径中的root bean, 叶子节点bean,无效值和key都必须实现Serializable接口
        • 如果用户希望远程发送一个ConstraintViolation对象,它应该必须确保对象图自身实现Serializable接口。

4.2.1、例子

  • 这些例子都是使用假设定义的@NotEmpty注解

  • package com.acme.constraint;
    
    @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 class Author{
        private String firstName;
        
        @NotEmpty(message="lastname must not be null")
        private String lastName;
        
        @Size(max=30)
        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;
      	}
      }
      
      
      public class Book{
        
        @NotEmpty(groups={FirstLevelCheck.class, Default.class})
        private String title;
        
        @Valid
        @NotNull
        private Author author;
        public String getTitle() {
              return title;
      	}
      	public void setTitle(String title) { 
          this.title = title;
      	}
        public Author getAuthor() {
              return author;
      	}
      	public void setAuthor(Author author) { 
          this.author = author;
      	}
      }
      
      Author author = new Author();
      author.setCompany("ACME");
      Book book = new Book();
      book.setTitle("");
      book.setAuthor(author);
      
      Set<ConstraintViolation> constraintViolations = validator.validate(book);
      
    • constraintViolations将会返回大小为2的集合,一个对象为表示title属性上的@NotEmpty(更具体应该是@Size(min=1)注解)

    • 这个失败的ConstraintViolation 对象将会通过如下断言

      • //假设是英语 locale, 内值的消息将会返回
        assert "may not be null or empty".equals(constraintViolation.getMessage());
        assert book === constraintViolation.getRootBean();
        assert book == constraintViolation.getLeafBean();
        // 无效值
        assert book.getTitle().equals(constraintViolation.getInvalidValue());
        //无效属性
        Iterator<Node> nodeIter = constraintViolation.getPropertyPath().iterator();
        assert "title".equals(nodeIter.next().getName());
        assert false == nodeIter.hasNext();
        
    • 另一个失败ConstraintViolation对象是lastname 的@NotEmpty( 根据具体是@NotNull 组成约束@NotEmpty), 失败的ConstraintViolation对象满足如下断言

      • assert "lastname must not be null".equals(constraintViolation.getMessage());
        assert book == constraintViolation.getRootBean();
        assert author == constraintViolation.getLeafBean();
        //无效值
        assert book.getAuthor().getLastName() == constraintViolation.getInvalidValue();
        //无效属性
        Iterator<Node> nodeIter =constraintViolation.getPropertyPath().iterator();
        assert "author".equals(nodeIter.next().getName());
        assert "lastName".equals(nodeIter.next().getName());
        assert false == nodeIter.hasNext();
        
        

4.3、消息补插(消息设置)

  • 一个消息补插就是约束的消息字符串转换为人类可读取的异常消息。

4.3.1、默认消息补插

  • 符合的实现包括默认消息插值器。该消息内插器应使用此处定义的算法将消息描述符内插到人类可读的消息中。

  • 每约束通过message属性定义一个消息的描述,每个约束都应该为这个约束定义一个默认的消息。消息也可以在定义约束时候定义一个property属性进行重写。

  • 消息描述符是字符串文字,并且可以包含一个或多个消息参数。消息参数是用括号括起来的字符串文字。以下字符转义适用:

    • \{被解析为{, 它表示的含义是消息参数开始位置
    • \}被解析为}, 它表示的含义是消息参数结束位置
    • \\被解析为\, 它表示一个转义字符。
  • 例子,消息使用参数

    • Value must be between {min} and {max}
      
4.3.1.1、默认消息补插算法
  • 使用以下步骤进行默认消息补插:
    1. 从消息字符串中提取消息参数,并将其用作关键字,以使用定义的语言环境搜索名为ValidationMessages(通常具体化为属性文件/ValidationMessages.properties 及其语言环境变体)的ResourceBundle。如果找到属性,则将消息参数替换为消息字符串中的属性值,递归应用步骤1,直到没有替换为止(即,消息参数值本身可以包含消息参数。
    2. 从消息字符串中提取消息参数,并将其用作关键字,以使用定义的语言环境搜索Bean验证提供程序的内置ResourceBundle(请参见下文),如果找到属性,则消息参数将替换为消息字符串中的属性值,与步骤1相反,没有对步骤2进行递归处理。
    3. 如果步骤2触发了更换,则将再次应用步骤1,否则执行步骤4.
    4. 从消息字符串中提取消息参数,那些与约束属性名称匹配的属性将由约束声明中该属性的值替换。
  • 定义locale如下
    • 如果一个local作为参数调用interpolate(String, Context, Locale),那么这个Locale实例将会被使用。
    • 否则,默认的Locale通过Locale.getDefault()获取
  • 所提出的算法确保在递归解析的所有级别上,自定义资源束始终比内置资源具有更高的优先级,它还确保了约束声明属性值不会进一步插值。

4.3.2、自定义消息补插器

  • 一个自定义消息补插器可能被提供(例如,补插上下文数据,或适配默认Locale, 一个消息补插器实现 MessageInterPolator接口

  • /**
    * 补插给定约束违例的消息。
    * 实现应该尽可能容忍语法错误
    *
    * @author Emmanuel Bernard
    * @author Hardy Ferentschik
    */
    public interface MessageInterpolator{
      
      /**
      * 补插消息模板是基于约束验证的上下文信息。
    	* 根据MessageInterpolator实现,locale是默认的,详情查看实现文档
    	* @param messageTemplate 消息插入模板
    	* @param context 关联补插器上下文信息
    	* @return 补插时异常信息
    	*/
      String interpolate(String messageTemplate, Context context);
      
      
      /**
      * 补插消息模板是基于约束验证的上下文信息。
      * Locale被作为参数传入
      * @param messageTemplate 消息插入模板信息
      * @param context 关联补插器上下文信息
      * @param locale 消息解析地区信息
      * @return 补插时异常信息
      */
      String interpolate(String messageTemplate, Context context, Locale locale);
      
      /**
      * 关联补插消息上下文信息
      */
      interface Context{
        
        /**
        *
        *@return ConstraintDescriptor 对应约束被验证
        *
        */
        ConstraintDescriptor<?> getConstraintDescriptor();
        
        /**
        * @return 返回已经验证的值
        */
        Object getValidatedValue();
      }
    }
    
  • messageTemplate是约束声明的消息属性的值,或提供给ConstraintValidatorContext方法。

  • Context对象包含关联补插的上下文信息

  • getConsraintDescriptor()方法返回ConstraintDescriptor对象表示失败约束的元数据(查看第5章)

  • getValidatedValue 表示值已经被验证过了。

  • MessageInterpolator.interpolate(String, Context),在生成约束违例报告都会调用这个方法,默认的Locale是特定于实现的。

  • 包装的MessageInterpolator可以调用MessageInterpolator.interpolate(String, Context, Locale),以通过绕过或覆盖默认的Locale策略来强制执行特定的Locale值(请参见示例4.3)

  • 一个消息补插器的实现应当是线程安全的

  • 如果补插器是由ValidatorFactory使用Configuration.messageInterpolator(MessageInterpolator)进行构造, 那么通过ValidatorFactory生成所有的Validator对象都可以共享这个消息补插器。

  • 通过调用ValidatorFactory.usingContext().messageInterpolator(messageInterpolator).getValidator(),可以重写给定Validator实例的MessageInterpolator实现。

  • 建议MessageInterpolator实现将最终插值委托给BeanValidation默认MessageInterpolator,以确保遵循标准的Bean Validation插值规则。可通过Configuration.getDefaultMessage()访问默认实现

  • 如果补插值时候发现异常,这个异常将会包装到ValidationException类型

4.3.3、例子

  • 这些里描述消息补插器是基于默认内置消息(详情查看附录B),ValidationMessages.properties文件展示内容如表4.2、这个当前语言环境假设英语环境

  • // ValidationMessages.properties
    myapp.creditcard.error=credit card number not valid
    
  • 表 4.2、消息补插器

    • 失败约束声明补插的消息
      @NotNullmust not be null
      @Max(30)must be less than or equal to 30
      @Size(min=5, max=15, message=“Key must have \\{{min}\\}\\ \\{{max}\\} characters”)Key must have{5}{15} characters
      @Digits(integer=9, fraction=2)numeric value out of bounds(<9 digits>.<2 digits> expected)
      @CreditCard(message={myapp.creditcard.error})credit card number not valid

      这是一种指定要在给定的验证程序上选择的Locale值的方法。支持区域设置的MessageInterpolator. 有关API的更多详细信息,请参见第4.4节

    • 例子4.3、 在使用用MessageInterpolator使用特定语言环境值(Locale)

    • /**
      * 委托给MessageInterpolator实现,但强制执行给定的Locale
      */
      public class LocaleSpecificMessageInterpolator implements MessageInterpolator{
        private final MessageInterpolator defaultInterpolator;
        
        private final Locale defaultLocale;
        
        public LocaleSpecificMessageInterpolator(MessageInterpolator interpolator, Locale locale){
          this.defaultLocale = locale;
          this.defaultInterpolator = interpolator;
        }
        
        /**
        *强制将locale作为参数传递给interpolator
        *
        */
        public String interpolate(String message, Context context){
          return defaultInterpolator.interpolate(message, context);
        }
        
        //没有真正的使用, 实现是为了完整性
        public String interpolate(String message, Context context, Locale locale){
          return defaultInterpolator.interpolate(message, context, locale);
        }
      }
      
      Locale locale = getMyCurrentLocale();
      MessageInterpolator interpolator = new LocaleSpecificeMessageInterpolator(validatorFactory.getMessageInterpolator, locale);
      
      Validator validator = validatorFactory.usingContext()
        	.messageInterpolator(interpolator)
        	.getValidator();
      
  • 但是,在大多数情况下,相关的语言环境将由您的应用程序框架透明地提供。该框架将实现自己的MessageInterpolator版本,并在ValidatorFactory配置期间将其传递。应用程序不必自行设置语言环境(Locale),此示例显示容器框架如何实现MessageInterpolator以提供用户特定的默认语言环境。

  • 例子4.4、 上下文可能容器MessageInterpolator实现

    • public class ContextualMessageInterpolator implements MessageInterpolator{
        
        private final MessageInterpolator delegate;
        
        public ContextualMessageInterpolator(MessageInterpolator delegate){
          this.delegate = delegate;
        }
        
        public String interpolator(String message, Context context){
          Locale locale = Container.getManager().getUserLocale();
          return this.delegate.interpolate(message, context, Locale);
        }
        
        
        public String interpolator(String message, Context context, Locale locale){
          return this.delegate.interpolate(message, context, locale);
        }
      }
      
      // 构建 ValidatorFactory
      Configuration<?> configuration = Validation.byDefaultProvider().configure();
      ValidatorFactory factory = configuration.messageInterpolator(new ContextualMessageInterpolator(configuration.getDefaultMessageInterpolator()));
      
      // 容器使用工厂通过特定的MessageInterpolator来验证约束
      

4.4、Bootstrapping (引导程序)

  • 引导程序API旨在提供一个ValidatorFactory对象,该对象用于创建Validator实例,引导过程与提供程序的初始化是分离的。引导实现必须能够引导任何Bean验证提供程序实现。引导程序序列以设计为实现几个目标:
    • 插入多个实现
    • 选择特定实现
    • 扩展性:使用特定提供程序实现的应用程序可以使用特定配置
    • 在验证器之间共享和重用元数据
    • 为实施留出尽可能多的自由
    • 提供与Java EE 6和其他容器的集成机制
    • 类型安全
  • 引导过程中涉及的主要工件有:
    • Validation: API入口点,使您可以选择定义目标的Bean验证提供程序以及提供程序解析策略。验证会生成配置对象, 并且可以引导任何提供程序实现。
    • ValidationProvider: 引导过程和Bean验证提供程序程序之间的契约
    • ValidationProviderResolver: 返回执行上下文(通常为类路径)中可用的所有Bean验证提供程序的列表。
    • Configuration: 收集将用于构建ValidatorFactory的配置详细信息。Bean验证提供程序必须提供Configuration的特定子接口,此子接口通常托管提供程序特定的配置。
    • ValidatorFactory: 引导过程的结果,从给定的Bean验证提供程序构建Validator实例。
    • META-INF/validation.xml: 一个配置文件,Bean Validation用户可以用来自定义默认ValidatorFactory的配置。
  • 首先,让我们通过一些示例了解API的实际作用,然后再深入探讨具体的定义。

4.4.1、例子

  • 最简单的方法是初始化默认的Bean验证提供程序或XML配置文件中定义的提供程序。然后, ValidatorFactory准备提供Validator实例。

  • 例子4.5、简单Bean验证引导程序序列

    • ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
      // cache the factory somewhere
      Validator validator = factory.getValidator();
      
    • ValidatorFactory 对象是线程安全的,生成验证器实例通常是一种轻量级的操作,构建ValidatorFactory通常更消耗更多资源,确保检查您的Bean验证实现文档以获取更准确的详细信息。

  • 第二示例显示了容器如何自定义一些Bean验证程序资源处理以匹配其自身的行为。

  • 例子4.6、自定义消息解析,遍历解析器和约束验证工厂实现

    • ValidatorFactory factory = Validation
        .byDefaultProvider().configure()
        .messageInterpolator(new ContainerMessageInterpolator())
        .constraintValidatorFactory(new ContainerComponentConstraintValidatorFactory())
        .traversableResolver(new JPAAwareTraversableResolver)
        .buildValidatorFactory();
      
      // 在某个地方缓存工厂类信息
      Validator vaildator = factory.getValidator();
      
  • 第三个示例显示了如何在不遵循传统Java类加载器策略(例如工具或OSGI等替代服务容器)环境中引导Bean验证,他们可以提供一些替代的提供程序解析策略来发现Bean验证提供程序。

  • 例子4.7、自定义Bean验证提供解决机制

    • // osgi environment
      ValidatorFactory factory = Validation
        .byDefaultProvider()
        	.providerResolver(new OSGiServiceDiscoverer())
        	.configure()
        		.buildValidatorFactory();
      // 在某个地方缓存工厂类信息
      Validator validator = factory.getValidator();
      
  • 下一个示例展示了客户端如何选择特定的Bean验证提供程序,并以类型安全的方式以类型安全的方式以编程方式配置特定于提供程序的属性。

  • 例子4.8、使用特定提供者和添加特定的配置

    • ValidatorFactory factory = Validation
        // 选择一个特定的提供者
        .byProvider(ACMEProvider.class)
        .configure()
        	//定义一个默认配置选项
        	.messageInterpolator(new ContraintMessageInterpolator())
        	.addConstraint(Address.class, customerConstraintDescriptor) //ACME 特定方法
        	.buildValidatorFactory();
      //相同化初始化分解调用
      ACMEConfiguration acmeConfiguration = Validation
        .byProvider(ACMEProvider.class)
        .configure();
      ValidatorFactory factory = acmeConfiguration
        .messageInterpolator(new ContainerMessageInterpolator()) // 默认配置选项
        .addConstraint(Address.class, customerConstraintDescriptor)// ACME specific method
        .buildValidatorFactory();
      
      /**
      * ACME 具体配置验证和配置选项
      *
      */
      public interface ACMEConfiguration extends Configuration<ACMEConfiguration>{
        
        /**
        * 以编程方式添加约束,特定于ACME提供者
        */
        ACMEConfiguration addConstraint(Class<?> entity, ACMEConstraintDescriptor constraintDescriptor);
      }
      
      /**
      * ACME 验证提供者
      * 注意:通过泛型参数将ACMEConfiguration和ACMEProvider连接起来
      */
      public class ACMEProvider implements ValidationProvider<ACMEConfiguration>{
        ....
      }
      
    • 最后一个例子展示一个Validator如何使用一个特定的MessageInterpolator实现

    • 例子4.9、 为给定的验证器使用特定的MessageInterpolator实例

      • ValidatorFactory factory = ...;
        MessageInterpolator customerInterpolator = new LocaleSpecificMessageInterpolator(
        	locale, factory.getMessageInterpolator()
        );
        Validator localizedValidator = 
          factory.usingContext().messageInterpolator(customeInterpolator).getValidator();
        
      • 以相同的方式,可以传递自定义的TraversableResolver

      • 现在,我们将探讨各种接口,它们的约束和用法,我们将从ValidatorFactory转到沿着引导链走的Validation类。

      4.4.2、ValidatorFactory

      • ValidatorFactory对象将构建并提供Validator的初始化实例给Bean Validation客户端。每个Validator实例都针对给定的上下文(消息插值器, 可遍历的解析器)进行配置。 客户端应缓存ValidatorFactory对象,并重新使用它们以获得最佳性能。该API旨在允许实现这在ValidatorFactory中共享约束元数据。

      • ValidatorFactory实现必须线程安全的,如果有必要 ValidatorFactory实现可以缓存Validator或实例

      • 例子4.10, ValidatorFactory 接口

        • /**
          * 工厂类返回一个初始化Validator实例, 实现必须是线程安全
          * 该对象通常被缓存和重用
          * @author Emmanuel Bernard
          */
          public interface ValidatorFactory{
            
            /**
            * 使用工厂默认的消息插值器、可遍历解析器、和约束验证工厂来实例化一个Validator对象
            * Validator实例可以放置到对象池中共享
            * @return 返回一个初始化的实例对象
            */
            Validator getValidator();
            
            
            /**
            * 定义一个新的validator上下文,validator需要兼容新的上下文
            * @return ValidatorContext
            */
            ValidatorContext usingContext();
            
            
            /**
            * 返回在ValidatorFactory在初始化时候设置的MessageInterpolator, MessageInterpolator将会在getValidator()方法中使用
          	* @return MessageInterpolator 实例
          	*/
            MessageInterpolator getMessageInterpolator();
            
            
             /**
            * 返回在ValidatorFactory在初始化时候设置的TraversableResolver, TraversableResolver将会在getValidator()方法中使用
          	* @return TraversableResolver 实例
          	*/
            TraversableResolver getTraversableResolver();
            
            
             /**
            * 返回在ValidatorFactory在初始化时候设置的ConstraintValidatorFactory, ConstraintValidatorFactory将会在getValidator()方法中使用
          	* @return ConstraintValidatorFactory 实例
          	*/
            ConstraintValidatorFactory getConstraintValidatorFactory();
            
            /**
            * 返回指定类型的实例,以允许访问提供程序特定的API。 如果Bean验证提供程序实现不支持指定的类型则抛出ValidationException,
            *@param type 返回对象类型
            *@ return 一个具体类的对象
            *@throws 如果提供者不支持这个调用将抛出ValidationException异常
            */
            public <T> T unwrap(Class<T> type);
          }
          
        • ValidatorFactory是由Configuration对象提供的。

        • 提供unwrap的一种方式来访问特定于Bean验证提供程序的给定类型的对象,通常是对ValidatorFactory的补充,使用此方法会使您的代码不可移植性

        • 例子4.11 使用unwrap方法去获取提供特定的合同

          • // 如果使用ACME提供者
            ACMEValidatorFactory acmeFactory = factory.unwrap(ACMEValidatorFactory.class);
            acmeFactory.setSpecificConfiguration(...);
            
          • getMessageInterpolator() 返回在ValidatorFactory初始化期间配置的MessageInterpolator实例。构建一个Validator特定的MessageInterpolator,将来自ValidatorFactory的消息包装起来特别有用

          • getTraversalbleResolver() 返回在ValidatorFactory初始化期间配置的TraversableResolver实例。构建一个Validator特定的TraversableResolver,将来自ValidatorFactory的消息包装起来特别有用

          • getConstraintValidatorFactory() 返回在ValidatorFactory初始化期间配置的ConstraintValidatorFactory实例。构建一个Validator特定的ConstraintValidatorFactory,将来自ValidatorFactory的消息包装起来特别有用

          • usingContext() 返回的ValidatorContext可用于自定义必须初始化Validator的状态。这用于自定义MessageInterpolator, TraversableResolver或 ConstraintValidatorFactory。

      • 例子4.12、 ValidatorContext接口

        • /**
          * 用于创建Validator的上下文实例
          * 客户端可能使用ValidatorFactory#usingContext返回ValidatorContext,然后可以自定义上下文去创建Validator实例(例如,建立不同的interpolators 或traversable resolvers)
          *
          * @author Emmanuel Bernard
          */
          public interface ValidatorContext{
            
            /**
            * 定义一个插值器给Validator使用,如果设置的值为null或没有设置,那么将默认使用
            * ValidatorFactory的中插值器对象
            * @return 支持链式算法,返回本对象
            *
            */
            ValidatorContext messageInterpolator(MessageInterpolator messageInterpolator);
            
            
            /**
            * 定义一个遍历解析器给Validator使用,如果设置的值为null或没有设置,那么将默认使用
            * ValidatorFactory的中遍历解析器对象
            * @return 支持链式算法,返回本对象
            *
            */
            ValidatorContext traversableResolver(TraversableResolver traversableResolver);
            
             /**
            * 定义一个约束验证工厂实例给Validator使用,如果设置的值为null或没有设置,那么将默认使用
            * ValidatorFactory的中约束验证工厂对象
            * @return 支持链式算法,返回本对象
            *
            */
            ValidatorContext constraintValidatorFactory(ConstraintValidatorFactory factory);
            
            /**
            * @return 返回自定义状态创建的Validator对象
            * Validator 实例可以放置到缓存池
            */
            Validator getValidator();
          }
          
        • ValidatorContext的MessageInterpolator、TraversableResolver和ConstraintValidatorFactory 替换 ValidatorFactory的MessageInterpolator、TraversableResolver和ConstraintValidatorFactory对象实例。

        • 例子4.13、使用ValidatorFactory

          • ValidatorFactory factory= ...
            Validator validatorusingDefaults = factory.getValidator();
            Validator validatorUsingCustomerTranserversable = factory.usingContext().traversableResolver(new JPATraversableResolver())
            .getValidator()
            
          • 查看Example 4.3 例子使用ValidatorFactory.getMessageInterpolator()

      4.4.3、(Configuration)配置

      • Configuration收集配置信息,确定正确提供者的实现,同时委派它创建ValidatorFactory创建, 这个类让你定义:

        • 消息插值器的策略实例
        • 遍历解析器的策略实例
        • 约束验证工厂实例
        • XML约束映射
        • 提供者的具体属性
        • 是否考虑使用META-INF/validation.xml
      • 配置确实遵循第4.3.1节中定义的默认Bean验证MessageInterpolator规则提供了MessageInterpolator实现,您可以通过调用getDefaultMessageInterpolator() 来访问它,这样的实现对于让自定义MessageInterpolator委托给标准MessageInterpolator很有用(请参阅第4.3.2节和示例4.4中使用getDefaultMessageInterpolator()的示例。

      • 配置确实按照第3.5.2节中定义的默认Bean验证TraversableResolver规则提供TraversableResolver实现。 您可以通过调用getDefaultTraversableResolver()来访问它,这样的实现对于让自定义TraversableResolver委托给标准TraversableResolver很有用。

      • 配置确实按照第3.5.2节中定义的默认Bean验证ConstraintValidatorFactory规则提供ConstraintValidatorFactory实现。 您可以通过调用getDefaultConstraintValidatorFactory()来访问它,这样的实现对于让自定义ConstraintValidatorFactory委托给标准ConstraintValidatorFactory很有用。

      • 客户端调用Configuration.buildValidatorFactory() 取出初始化ValidatorFactory实例

      • 例子4.14、Configuration 接口

        • /**
          * 接收配置信息,选择适当的Bean验证提供程序,并构建适当的ValidatorFactory
          * 提供一个Validation 引导方法
          * Configuration<?> configuration = 
          * ValidatorFactory = configuration.messageInterpolator(new CustomerMessageInterpolator()).buildValidatorFactory();
          * 在默认情况下,这个配置信息来自与META-INF/validation.xml, 可以使用configuration的方法去重写xml的信息
          * ValidationProviderResolver是在配置时指定(详情看javax.validation.spi.ValidationProvider), 如果没有明确要求,则使用默认的ValidationProviderResolver
          *	这个provider通过如下方式进行选择
          *1. 如果具体provider通过编程方式调用Validation.byProvider(Class),那么就将会找到第一个实现这个provider类请求和使用它
          *2、如果具体provider请求在META-INF/validation.xml中,那么就将会在文件中找到第一个实现这个provider类请求和使用它
          *3、其他情况使用第一个provider将会返回ValidationProviderResolver
          *
          *实现类并不意味是线程安全的
          *
          * @author Emmanuel Bernard
          */
          
          public interface Configuration<T extends Configruation<T>>{
            
            /**
            * 如果这个方法被调用那么将会忽略META-INF/validation.xml文件的数据
            * 此方法通常对解析的容器有用 META-INF/validation.xml 并通过Configuration方法传递信息
            *
            *@return this, 支持链式方法模式
            */
            T ignoreXmlConfiguration();
            
           /**
            * 定义一个消息插值器,它优先级高于基于配置的消息插值器
            * 如果设置null,则使用默认消息插值器(定义在XML中或规范默认的
            *@param interpolator 消息插值器的实现
            *@return this, 支持链式方法模式
            */
            T messageInterpolator(MessageInterpolator interpolator);
            
            /**
            * 定义一个遍历解析器,它优先级高于基于配置的遍历解析器
            * 如果设置null,则使用默认遍历解析器(定义在XML中或规范默认的
            *@param resolver 遍历解析器的实现
            *@return this, 支持链式方法模式
            */
            T traversableResolver(TraversableResolver resolver);
            
             /**
            * 定义一个约束验证工厂器,它优先级高于基于配置的遍历解析器
            * 如果设置null,则使用默认约束验证工厂器(定义在XML中或规范默认的
            *@param constraintValidatorFactory 约束验证工厂器的实现
            *@return this, 支持链式方法模式
            */
            T constraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory);
            
            /**
            * 在Bean验证中添加XML格式约束映射的流
            * 构建ValidatorFactory后,客户端API应关闭流,Bean验证提供程序不得关闭流
            *
            *@param stream xml 映射流
            *@return this, 支持链式方法模式
            *@throws IllegalArgumentException 如果stream字段为null 抛出异常
            */
            T addMapping(InputStream stream);
            
            
             /**
            * 添加一个提供者特定属性,这个属性等同于 XML配置属性
            * 如果提供者不用这个属性,那么这个属性将被忽略
            * 注意:这个是一个非类型安全的方法, 通常不推荐使用的
            * 如果可用,则更适合使用特定提供程序通过其Configuration子类提供的类型安全等效项, ValidatorFactory factory = Validation.byProvider(ACMEPrivoder.class).configure().ProviderSpecificProperty(ACMEState.FAST).buildValidatorFactory();
            * 容器本身解析META-INF/validation.xml 并将状态注入Configuration对象时,通常使用此方法
            * 如果即通过此方法又在XML配置中定义了具有给定名称的属性,则以编程方式设置的值具有优先级。
            * 如果将null作为值传递,则使用XML定义的值。如果没有价值以XML定义,则该属性被视为未设置
            *
            *
          	*@param name 属性名称
            *@param value 属性的值
            *@return this, 支持链式方法模式
            *@throws IllegalArgumentException 如果stream字段为null 抛出异常
            */
            T addProperty(String name, String value);
            
            
            /**
            * 根据规范中定义的默认MessageInterpolator返回MessageInterpolator接口的实现
            * 使用 ValidationMessages 资源束去加载keys
            * 使用 Locale.getDefault()
            * @return 返回符合规范的默认MessageInterpolator实现
            *
            */
            MessageInterpolator getDefaultMessageInterpolator();
            
            
             /**
            * 根据规范中定义的默认TraversableResolver返回TraversableResolver接口的实现
            * 如果Java Persistence在运行时环境中可用,则如果Java Persistence 将该属性视为已加载,则认为属性是可访问的
            * 如果Java Persistence在运行时环境中不可用,则所有属性均被认为可访问
            * 所有属性都被认为是可级联的
            * @return 返回符合规范的默认TraversableResolver实现
            *
            */
            TraversableResolver getDefaultTraversableResolver();
            
             
             /**
            * 根据规范中定义的默认ConstraintValidatorFactory返回ConstraintValidatorFactory接口的实现
            *  使用ConstraintValidator的无参构造函数
            * @return 返回符合规范的默认ConstraintValidatorFactory实现
            *
            */
            ConstraintValidatorFactory getDefaultConstraintValidatorFactory();
            
            /**
            * 构建一个ValidatorFactory实现
            * @return ValidatorFactory
            * @throws ValidationException 如果ValidatorFactory不能构建
            */
            ValidatorFactory buildValidatorFactory();
          }
          
      • Bean 验证提供程序必须定义唯一标识提供程序的Configuration子接口,该子接口通过ValidationProvider通用参数链接到其提供程序,Configuration子接口通常托管提供程序特定的配置方法。

      • 为了方便使用提供程序特定的配置方法,Configuration使用泛型: Configuration <T extends Configuration> ; 通用返回类型T通过链接方法返回。提供特定的子接口必须将通用T解析为自身,如示例4.15所示

      • 例子 4.15 提供具体子类接口

        • /**
          * ACME提供程序的唯一标识符还承载某些提供程序特定的配置方法
          *
          */
          public interface ACMEConfiguration extends Configuration<ACMEConfiguration>{
            
            /**
            * 使用ACME默认为false时启用约束实现动态重新加载
            */
            ACMEConfiguration enableDynamicReloading(boolean a);
          }
          
      • 调用Configuration.buildValidatorFactory()时,将返回初始化的ValidatorFactory.更具体地说,确定请求的Bean验证提供程序,并返回validationProvider.buildValidatorFactory(ConfigurationState)的结果。ConfigurationState允许访问META-INF/validation.xml中定义的配置信息(除非忽略XML配置),并以编程方式提供给Configuration.一般来说,以编程方式定义的元素比通过XML定义的配置元素具有优先权(请阅读Configuration Java Doc 并参阅第4.4.6节以获取更多信息)。

      • 注意:

        • Configuration的典型实现也实现ConfigurationState,因此可以将其传递给buildValidatorFactory(ConfigurationState)
      • 在ValidatorFactory创建之后(或者如果发生异常)必须由Configuration实现关闭以XML配置表示的并且由Configuration实现打开的流。以编程方式提供的流是应用程序的职责。

      • 例子4.16.ConfigurationState 接口

        • /**
          * Configuration和ValidatorProvider之间的契约以创建ValidatorFactory。 XML配置中定义并提供给Configuration的配置工件被合并并通过ConfigurationState传递
          *
          *
          * @author Emmanuel Bernard
          * @author Hardy Ferentschik
          */
          public interface ConfigurationState{
            
            /**
            * 如果Configuration.ignoreXMLConfiguration()被调用返回true,那么ValidatorFactory 一定忽略 META-INF/validation.xml
            * @return true, META-INF/validation.xml应该被忽略
            *
            */
            boolean isIgnoreXmlConfiguration();
            
            
            /**
            * 返回配置对象中的消息插值器,消息插值器按照接来的顺序优先级递减:
            *	1、通过Configuration编程设置
            * 2、定义在META-INF/validation.xml中,同时 ignoreXmlConfiguration返回false, 在这种情况下通过无参构造方法创建实例对象
            * 如果没有定义返回null
            * @return mesageInterpolator, 如果没有返回null
            */
            MessageInterpolator getMessageInterpolator();
            
            
            /**
            * 返回一个配置的集合流
          	* 这些流通过如下定义:
          	* 1. Configuration编程设置的
          	* 2. META-INF/validation.xml定义的(constraint-mapping 元素)
          	* XML配置中表示并由Configuration实现打开的流, 必须由创建ValidatorFactory后(或如果发生异常)的Configuration实现。
          	* @return 输入流的集合
          	*/
            Set<InputStream> getMappingStreams();
            
            
            /**
            * 从配置中返回约束验证工厂。
            * 实现定义优先级如下降序:
            * 1. 通过Configuration编程方式设置
            * 2. 定义在META-INF/validation.xml, 且ignoredXmlConfiguration 返回false,在这种情况下使用的无参的构造方法
            * 如果未定义将返回null
            * @return 工厂实例或如果没定义返回null
            */
            ConstraintValidatorFactory getConstraintValidatorFactory();
            
             
            /**
            * 从配置中返回遍历解析器。
            * 实现定义优先级如下降序:
            * 1. 通过Configuration编程方式设置
            * 2. 定义在META-INF/validation.xml, 且ignoredXmlConfiguration 返回false,在这种情况下使用的无参的构造方法
            * 如果未定义将返回null
            * @return 遍历解析器实例或如果没定义返回null
            */
            TraversableResolver getTraversableResolver();
            
               
            /**
            * 返回一个非类型安全的自定义属性的map
            * 属性定义如下:
            * 1. 通过Configuration.addProperty(String,String)编程方式设置
            * 2. 定义在META-INF/validation.xml, 且ignoredXmlConfiguration 返回false
            * 如果同一个属性即在编程方式和XML定义,那么编程方式的优先级更高一些
            * @return 返回Map, key属性的key, value 为属性的value
            */
            Map<String, String> getProperties();
            
          }
          
      • 根据以下规则,按以下顺序解析请求的提供程序实现:

        • 如果已经从Validation.byProvider(Class)创建了Configuraion,则使用请求的提供程序实现
        • 如果已定义,请使用XML配置中描述的提供程序实现(在validation-config.default-provider下,请参见第4.4.6节):该元素的值是唯一标识提供程序的ValidationProvider实现的完全限定的类名。
        • 使用由validationProviderResolver.getValidationProviders()返回的第一个提供程序实现。
      • 当Configuration实例创建时候(查看ValidatorProvider) 那么ValidationProviderResolver被指定,如果没有ValidationProviderResolver实例被指定,那么将会使用默认的ValidationProviderResolver

      • Configuration实例通过Validation方法提供给Bean Validation客户端。配置实例由ValidationProvider创建。

      • 如果在构建ValidatorFactory出现问题,ValidationException将会抛出,具体原因可能是如下所示:

        • 格式错误的XML配置
        • 格式错误的XML映射
        • 没有能力找到provider
        • 无法实例化XML配置中提供的扩展类
        • XML映射不一致(多次声明实体,不正确的字段等)
        • 无效约束声明或定义
      • 可能会发生其他异常原因

      • 这里有配置使用的例子(4.17)

        • Configuration<?> configuration = ...;
          ValidatorFactory factory = configuration
            .messageInterpolator(new WEMessageInterpolator())
            .traversableResolver(new JPAAwareTraversableResolver())
            .buildValidatorFactory();
          

4.4.4、 ValidationProvider 和 ValidationProviderResolver

  • ValidationProvider是引导过程和特定Bean验证提供程序之间的契约,ValidationProviderResolver 为Bean验证提供程序实现发现机制。任何Bean验证客户端都可以实现这种发现机制。但通常是由具体特定类加载器结构和限制的容器来实现的。
4.4.4.1、ValidatorProviderResolver
  • ValidatorProviderResolver返回运行时可用的Bean验证提供程序的列表,更具体地说,返回上下文中可用的每个提供程序的ValidatorProvider实例,可以通过实现ValidationProviderResolver来定制此服务,实现必须是线程安全的。

  • 例子4.18、 ValidationProviderResolver

    • /**
      *在运行时环境中定义可用Bean 验证提供者列表
      * Bean验证提供程序通过以下方式标识:
      * 1. 遵循服务提供者模式的META-INF/services/javax.validation.spi.ValidationProvider文件描述了
      http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider
      *2、 每个META-INF/services/javax.validation.spi.ValidationProvider文件均包含以下列表:
      * ValidationProvider实现分别代表一个提供者,实现必须是线程安全的
      *
      * @author Emmanuel Bernard
      */
      public interface ValidationProviderResolver{
        
        /**
        *在运行时环境中返回可用的ValidatorProviders列表
        * @return 可用providers列表
        */
        List<ValidationProvider<?>> getValidationProvider();
      }
      
  • 默认情况下,将使用http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider 中描述的服务提供程序模式来解析提供程序。Bean验证提供程序必须通过创建文本文件javax.validation.spi.ValidationProvider并将其放置在其JAR文件之一的MATE-INF/services 目录中来提供服务提供程序配置文件,文件的内容应包含javax.validation.spi.ValidationProvider接口的提供程序实现的名称

  • 可以使用与其他服务提供商相同的方式来安装Bean验证提供程序JAR或使其可用。作为扩展,或根据JAR文件规范中的准则添加到应用程序类路径中。

  • 默认的ValidationProviderResolver实现将通过类路径中可见的提供程序配置文件来查找所有Bean验证提供程序,建议使用默认的ValidationProviderResolver实现,很少使用自定义的ValidationProviderResolver实现。自定义分辨率典型用法是在像OSGI这样的类加载器受限的容器中或在工具环境(IDE)中解析提供程序。

  • 可以通过BootStrapState.getDefaultValidationProviderResolver() 访问默认的ValidationProviderResolver. Bean验证提供程序配置实现通常使用此方法。

4.4.4.2、ValidationProvider
  • ValidationProvider表示SPI(服务提供者接口),它定义了提供者发现和初始化机制与提供者之间的协定。ValidationProvider会执行以下操作:

    • 提供一个泛型的Configuration实现(即不绑定给提供者)
    • 提供一个提供者特定Configuration实现, 这个Configuration将会构建ValidatorFactory实例
    • 从ConfigurationState提供的配置中构建ValidatorFactory对象。
  • 例子 4.19 ValidationProvider

    • package javax.validation.spi;
      /**
      * 验证引导程序机制和提供程序引擎之间的契约
      * 实现必须是一个无参构造方法,提供者的构造方法应该是轻量级的
      * T 代表提供者具体Configuration子类,通常是托管提供商的其他配置方法。
      * @author Emmanuel Bernard
      * @author Hardy Ferentschik
      *
      */
      public interface ValidationProvider<T extends Configuration<T>>{
        
        
        /**
        * 返回一个实现T子接口的Configuration实例, 返回Configuration实例必须
        * 使用当前提供者this 去构建ValidatorFactory实例
        * @param state bootstrap state
        * @return 特定配置的实现
        */
        T createSpecializedConfiguration(BootstrapState state);
        
        
        /**
        * 返回一个Configuration实例,这个实例没有绑定使用当前提供者, 选择提供者需要遵循(javax.validation.Configuraion的)算法
        * Configuration 使用ValidationProviderResolver是由state提供。
        * 如果为null, 将会使用默认的ValidationProvider
        * @param  state bootstrap state
        * @return 不是具体配置实现
        */
        Configuration<?> createGenericConfiguration(BootstrapState state);
        
        /**
        * 使用当前提供者实现来构建一个ValidatorFactory
        * ValidatorFactory组装需要传入configurationState参数
        * 返回ValidatorFactory已经正确初始化并可以使用。
        * @param configurationState  configuration描述文件
        * @throws 如果ValidatorFactroy 不能构建则抛出javax.validation.ValidationException 异常
        *
        */
        ValidatorFactory buildValidatorFactory(ConfigurationState configurationState)
        
      }
      
  • 例子 4.20, BootstrapState 接口

    • package javax.validation.spi;
      /**
      * 定义state用于引导Configuration对象
      *
       * @author Emmanuel Bernard
       * @author Sebastian Thomschke
      *
      */
      
      public interface BootstrapState{
        
        /**
        * 使用已经定义的ValidationProviderResolver策略实例,如果没有定义返回null
        *
        * @return ValidationProviderResolver 实例或null
        */
        ValidationProviderResolver getValidationProviderResolver();
        
        
        /**
        *
        * 规范默认的ValidationProviderResolver
        * @return 默认实现的ValidationProviderResolver
        *
      	*/
        ValidationProviderResolver getDefaultValidationProviderResolver();
      }
      
    • 一个客户端可以通过使用<T extends Configuration, U extends ValidationProvider> Validation.byProvider(Class) 或定义在XML配置文件中来请求一个特定 Bean Validation 提供者。唯一key标识Bean验证提供程序的是特定于该提供程序的ValidationProvider实现。

    • 一个ValidationProvider实现被链接到一个Configuration具体的子接口(通过他泛型参数) 当一个具体提供者被请求时候,这个Bean Validation 引导API通过链接方式返回类型安全的具体Configuration子类接口实现,子接口并没有强制添加任何新的方法,而是提供者特定配置方法的自然持有者。

    • 例子4.21、 提供具体配置子类接口

      • public interface ACMEConfiguration extends
          Configuration<ACMEConfiguration>{
          
          /**
          * 使用ACME时启用约束实现动态重新加载,默认为false
          *
          */
          ACMEConfiguration enableDynamicReloading(boolean);
          
        }
        
        /**
        * ACME 验证提供者
        * 注意: 通过泛型参数将ACMEConfiguration 和ACMEProvider链接在一起
        *
        */
        public class ACMEProvider implements ValidationProvider<ACMEConfiguration>{
          ...
        }
        
      • 备注:

      • Configuration在泛型定义中引用自身,Configuration方法将返回ACMEConfiguration,从而使该API易于使用,甚至适用于特定于供应商的扩展

    • 提供者发现机制使用如下算法规则:

      • 使用ValidationProviderResolver.getValidationProviders()取出可用的提供者。
      • 返回与请求的提供程序匹配的第一个ValidationProvider. 提供程序按照ValidationProviderResolver返回的顺序进行评估,如果提供程序实例可分配给所请求的提供程序类,则认为该提供程序实例是匹配的。
    • 当请求默认的Bean验证提供程序时,将返回ValidationProviderResolver策略返回的第一个ValidationProvider。

    • 每个Bean验证提供程序都必须提供一个包含公共无参构造函数的ValidationProvider实现,并在其Jar之一中添加相应的META-INF/services/javax.validation.spi.ValidationProvider文件描述符。

    • 如果在构建ValidationFactory时发生问题,则会引发ValidationException,这可能由于多种原因造成的,包括:

      • XML格式错误映射
      • 不能找到provider
      • 无法实例化XML配置中提供的扩展类
      • 不一致的XML映射(多次声明实体,不正确的字段等)
      • 无效约束声明或定义。

4.4.5、Validation(验证)

  • Validation 类是用于引导Bean Validation提供程序的入口点,第一个入口buildDefaultValidatorFactory()被认为是默认的ValidatorFactory, 它等效于Validation.byDefaultProvider().configure().buildValidatorFactory()返回的ValidatorFactory。

  • 警告

    • 解析策略应该通过XML配置。
  • 例子4.22, Validation方法的可得

    • 
      /**
      * 这个类是Bean Validation的入口点,这里有三种方法引导它:
      * 1、第一种方式:最容易途径构建默认ValidatorFactory对象(
      ValidatorFactory factory = Validation.buildDefaultValidatorFactory();),
      在这种情况下,默认验证提供者解析将会使用和定位可用的提供者。选择一个可用如下进行定义:
      		1. 如果xml配置定义一个提供者,那么这个提供者将会使用。
      		2.如果 xml配置没有定义一个提供者,则ValidationProviderResolver实例返回被使用。
      *
      * 2、 第二种方式 允许选择一个自定义的ValidationProviderResolver类,那么以相同方式确定:ValidationProvider
      *  Configuration<?> configuration = Validation
      				.byDefaultProvider()
      				.providerResolver(new MyReasolverStrategy())
      				.configure();
      *3、第三种方式 允许你以类型安全的方式显式指定期望的Provider, 你可以选择自定义的ValidationProviderResolver
      *	  ACMEConfiguration configuration = Validation
      				.byProvider(ACMEProvider.class)
      				.providerResolver(new MyResolverStrategy())
      				.configure();
      *  ValidatorFactory factory = configuration.buildValidatorFactory();
      *
      * 引导过程中构建ValidatorFactory对象应该缓存起来和在多个Validator消费者之中共享。
      * 这个类是一个线程安全的
      *
      *
      * @author Emmanuel Bernard
      * @author Hardy Ferentschik
      */
      public class Validation{
        
        
        /**
        *  根据默认的Bean Validation provider 并遵循XML配置,生成
        并返回一个ValidatorFactory 实例
        * 使用默认的验证提供程序resolver逻辑来解析provider列表
        * 这个代码语法等同于 Validation.byDefaultProvider().configure().buildValidatorFactory()
        *
        *@return ValidatorFactory 实例
        *@throws 如果ValidatorFactory不能构建则抛出ValidationException 异常
        */
        public static ValidatorFactory buildDefaultValidatorFactory(){
          ...
        }
        
        
        
        /**
        * 构建一个Configuration, 使用策略解析provider列表提供到引导状态
      	* Configuration<?> configuration = Validation
      			.byDefaultProvider()
      			.providerResolver(new MyResolverStrategy())
      			.configure();
      		ValidatorFactory factory = configuration.buildValidatorFactory();
      	* 这个provider被指定在xml配置文件中,如果xml不存在,则第一个可用provider将会返回
      	*	@return 实例, 构建一个泛型Configuration去兼容引导状态提供者
      	*/
        public static GenericBootstrap byDefaultProvider(){
          ...
        }
        
        
        /**
        * 为特定的provider实现构建一个Configuration类。
        * 可选的是重新提供解决策略使用确定provider
        * 由以编程方式定位到特定provider应用程序使用
        * ACMEConfiguration configuration = 
        				Validation.byProvider(ACMEProvider.class)
        				. providerResolver(new MyResolverStrategy())
        				.configure();
        * ACMEConfiguration 是Configuration的子接口,唯一标识到ACME Bean Validtion provider, ACMEProvider 是 ValidationProvider实现了ACME的provider
        * @param providerType ValidationProvider实现这个类型
        * @return 实例化构建一个provider 具体Configuration 子接口实现
        *
      	*/
        public static <T extends Configuration<T>, U extends ValidationProvider<T>> ProviderSpecificBootstrap<T> byProvider(Class<U> providerType)){
          ...
        }
        ...
      }
      
  • 第二个入口点让客户端提供一个自定义的ValidationProviderResolution 实例。 这个实例作为参数传递到GenericBoostrap.GenericBootstrap构建一个泛型Configuration,它将调用ValidationProvider.createGenericConfiguration(BootstrapState state).使用第一个ValidationProviderResolution的ValidationProvider。 BootstrapState 包含 ValidationProviderResolution实例将作为参数传递给GenericBoostrap, 它将使用Configuration实例。主要: ValidationProvider.createGenericConfiguratoin 返回一个Configuration对象不会绑定任何特定provider

  • 例子 4.23、GenericBootstrap 接口

    • 
      /**
      * 定义一个状态给引导Bean Validation使用 和创建一个provider 诊断Configuration
      *
      *
      * @author Emmanuel Bernard
      */
      public interface GenericBootstrap{
        
        /**
        * 定义provider的解决策略
        * 这个解析返回一个provider列表去评估构建Configuration
        * 如果解析对象没有定义, 那么默认ValidationProviderResolver实现
        *
        * @return this 返回调用链方法
        */
        GenericBootstrap providerResolver(ValidationProviderResolver resolver);
        
        
        /**
        * 返回一个泛型Configuration的实现
        * 在这个阶段provider用于构建ValidatorFactory, 如果provider没有被定义,那么 Configuration实现ValidationProviderResolver提供第一个provider返回
      	* @return  一个configuration实现兼容引导状态
      	* @throws javax.validation.ValidationException 如果这个配置对象不能构建, 这通常是由于ValidationProvider的问题
      	*
      	*/
        Configuration<?> configure();
        
      }
      
  • 最后一个入口点使客户端可以定义所请求的特定Bean验证提供程序以及自定义的ValidationProviderResolver实现(如果需要)。 入口点方法 Validation.byProvider(Class providerType) 接受提供者特定的ValidationProvider实现类型,并返回一个ProviderSpecificBootstrap对象,该对象保证返回特定Configuration子接口的实例。 由于使用了泛型,因此客户端API不必强制转换为Configuration子接口。

  • ProviderSpecificBootstrap对象可以选择接收ValidationProviderResolver实例。

  • 例子4.24 ProviderSpecificBootstrap 接口

    • package javax.validation.bootstrap
      /**
      * 定义用于引导Bean验证的状态,并创建特定于provider的Configuration类型为T
      * 特定Configuration通过ValidationProvider的泛型参数链接到provider上
      * 请求provider是第一个provider实例分别给请求provider的类型(当ProviderSpecificBootstrap构建时候可以知道provider类型)
      * ValidationProviderResolver可以解析providers列表
      * 如果没有ValidationProviderResolver没有被定义,将会使用默认的ValidationProviderResolver 策略使用。
      *
      * @author Emmanuel Bernard
      */
      
      public interface ProviderSpecificBootstrap<T extends Configuration<T>> {
        
        
        /**
        * 可选自定义定义provider解析类, 如果没有定义将会使用默认ValidationProviderResolver
        * @param  resolver ValidationProviderResolver实现类
        * @return this 支持链式模式
        *
        */
        public ProviderSpecificBootstrap<T> providerResolver(ValidationProviderResolver resolver);
        
        
        /**
        * 确定适合于T的provider实现,并将此特定Configuration资料的创建委托给provider
        * @return Configuration子类实现
        * @throws  javax.validation.ValidationException, 如果Configuraion对象不能构建时候抛出
        *
        */
        public T configure();
      }
      
  • ProviderSpecificBootstrap.configure()必须返回ValidationProvider.createSpecializedConfiguration(BootstrapState状态)的结果, state参数保存传递给ProviderSpecificBootstrap的ValidationProviderResolver. 所使用的验证提供程序实例是可分配给在Validation.byProvider(Class)中作为参数传递的类型的实例。 验证提供者是根据(第4.4.4.2节)中描述的算法选择的。

  • 除了三个公共静态引导程序方法之外,Validation实现不得包含任何非私有属性或方法:

    • public static ValidatorFactory buildDefaultValidatorFactory()
    • public static GenericBootstrap byDefaultProvider()
    • public static <T extends Configuration, U extends ValidationProvider> ProviderSpecificBootstrap byProvider(Class providerType)
  • 引导API旨在允许Bean验证provider实现之间的完全可移植性。引导实现必须确保它可以引导第三方providers

  • 当构建Configuration对象,如果ValidationProviderResolver 失败或没有找到期望的provider, 那么一个ValidationException 被抛出

4.4.6、XML配置:META-INF/validation.xml

  • 除非通过调用Configuration.ignoreXMLConfiguration()显式忽略, 否则配置将考虑META-INF/validation.xml中可用的配置,该配置文件是可选的,但可由应用程序用来完善某些Bean验证行为。如果在类路径中找到多个META-INF/validation.xml文件,则会引发ValidationException.

  • 除非另有说明,否则基于XML的配置设置将被通过Configuration API明确设置的值覆盖。例如。通过Configuration.messageInterpolator(MessageInterpolator)定义的MessageInterpolator 优先于messageInterpolator定义。

  • default-provider: 表示provider特定的ValidationProvider实现了的类名称。如果定义,则使用特定的provider(除非已通过编程方法选择了特定的provider),也就是编程优先级高于xml配置的

  • message-interpolator: 表示MessageInterpolator实现的完全限定的类名。当以XML定义时,实现必须具有公共的无参构造函数。该元素是可选的

  • traversable-resolver: 表示TraversableResolver实现的完全限定的类名。当以XML定义时,实现必须具有公共的无参数构造函数,该元素是可选的。

  • constraint-validator-factory:表示ConstraintValidtorFactory实现的完全限定的类名。当以XML定义时,实现必须具有公共的无参数构造函数,该元素是可选的。

  • constraint-mapping: 表示XML映射文件的资源路径。可以存在多个约束映射元素,通过Configuration.addMapping(InputString)提供的映射将添加到通过约束映射描述的映射列表。

  • property: 表示键/值对属性,为provider特定的配置提供命名空间,供应商应使用供应商命名空间作为属性(例如, com.acme.validation.logging). 使用命名空间javax.validation及子命名空间条目不得用于特定供应商的信息, 命名空间javax.validation保留供本规范使用。通过Configuration.addProperty(String, String)定义的属性将添加到通过property定义属性中。如果在XML和通过编程API中都定义了具有相同名称的属性,则通过编程API提供的值具有优先权。

  • 如果无参构造函数不存在,在调用Configuration.buildValidatorFactory()抛出ValidationException 异常

  • 例子 4.25. META-INF/validation.xml文件

    • <?xml version="1.0" encoding="UTF-8"?> 
      <validation-config
      xmlns="http://jboss.org/xml/ns/javax/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=
      "http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.0.xsd">
        <default-provider>com.acme.ACMEProvider</default-provider> 
        <message-interpolator>com.acme.ACMEAwareMessageInterpolator</message-interpolator>
      	<constraint-mapping>META-INF/validation/order-constraints.xml</constraint-mapping>
        <constraint-mapping>META-INF/validation/catalog-constraints.xml</constraint-mapping> 
        <constraint-mapping>META-INF/validation/customer-constraints.xml</constraint-mapping>
      	<property name="com.acme.validation.logging">WARN</property>
      	<property name="com.acme.validation.safetyChecking">failOnError</property>
      </validation-config>
      
    • XML 模式在7.2节中描述

4.4.7、使用

  • Bean验证引导API可以由应用程序直接使用,可以通过使用容器需要验证的框架来使用,在所有情况下,以下规则均适用
    • ValidatorFactory 是一个线程安全对象,在每个部署单元构建一次
    • Validator是线程安全的应被视为轻量级对象。如果需要ValidatorFactory通常会实施适当的Validator实例缓存策略。
  • 鼓励诸如Java EE,依赖注入框架,组件框架之类的容器以遵从先前规则的方式建议对ValidatorFactory和Validator对象的访问,例如应该可以注入Validator
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值