Hibernate Validator

摘要:

在项目的业务属性中,你是不是要经常验证属性的取值范围呢. 想要了解比较优美的解决方案吗? 看看Hibernate Validator 是怎么做的吧.一见到她,相信你就会说: Oh God, 这就是我需要的.

作者:icess(作者的blog:http://blog.matrix.org.cn/page/icess)
关键字:Hibernate Validator
              
用Annotations 给类或者类的属性加上约束(constraint),在运行期检查属性值是很优雅的.Hibernate Validator就是这样的一个框架.该框架是十分容易的(就像参考文档中宣称的那样),几乎没有什么学习曲线,Validator 是一个验证框架 不需要和Hibernate的其他部分绑定就可以使用,只要在你的项目中添加Hibernate-annotations.jar库就可以了.那么下面就 让我们看看怎么使用吧.

Person.java 类
/*
 * Created on 2006-1-12 Person.java
 * @author 
 */
package test.annotation.validator;

import org.hibernate.validator.Length;
import org.hibernate.validator.Min;
import org.hibernate.validator.Valid;
 

//@Serializability  //测试自定义约束
public class Person {

  private String name;
  private int age;
  private Address address;
  
  public Person() {}
  
  @Valid //注意此处
  public Address getAddress() {
    return address;
  }
  public void setAddress(Address address) {
    this.address = address;
  }
  
  @Min(value = 1)
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
  
  @Length(min = 4)
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
} 

Address.java 类

/*
 * Created on 2006-1-12 Address.java
 * @author 
 */
package test.annotation.validator;

import org.hibernate.validator.Length;
import org.hibernate.validator.Max;
import org.hibernate.validator.Min;

public class Address {

  private String street;
  private int num;
  
  public Address() {}
  
  @Min(value = 1)
  @Max(value = 100)
  public int getNum() {
    return num;
  }
  public void setNum(int num) {
    this.num = num;
  }
  
  @Length(min = 3,max = 8)
  public String getStreet() {
    return street;
  }
  public void setStreet(String street) {
    this.street = street;
  }
} 
上面是两个用 Validator Annotations 注释的 类. 每个属性都用 约束限制了.  下面看看测试的类吧:


TestValidator.java 类

/*
 * Created on 2006-1-12
 * @author icerain
 */
package test.annotation.validator;

import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;


public class TestValidator {
  public void test() {
    Address add = new Address();
    add.setNum(0);
    add.setStreet("1");
    
    Person p = new Person();
    p.setAddress(add);
    p.setAge(0);
    p.setName("ice");
    
    /******************Test validator ********/
    // 注意该处只验证了Person 为了说明 @Valid 注释的使用
    ClassValidator<Person> classValidator = new ClassValidator<Person> (Person.class);
    InvalidValue[] validMessages = classValidator.getInvalidValues(p);
    for (InvalidValue value : validMessages) {
      
    System.out.println("InvalidValue 的长度是:" + validMessages.length
        +" . 验证消息是: " + value.getMessage() 
        + " . PropertyPath 是:" + value.getPropertyPath()
        +" .\n\t PropertyName 是: " +value.getPropertyName()
        + "Value 是: " + value.getValue()
        +" Bean 是: "+ value.getBean()
        +"\n\t BeanClass 是:" + value.getBeanClass());
    }
  }
  
  public static void main(String[] args) {
    new TestValidator().test();
  }
}
程序的输出如下 
InvalidValue 的长度是:4 . 验证消息是: 必须大于等于 1 . PropertyPath 是:age .

PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@dd87b2

BeanClass 是:class test.annotation.validator.Person

InvalidValue 的长度是:4 . 验证消息是: 长度必须介于 4 与 2147483647 之间 . PropertyPath 是:name .

PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@dd87b2

BeanClass 是:class test.annotation.validator.Person

InvalidValue 的长度是:4 . 验证消息是: 必须大于等于 1 . PropertyPath 是:address.num .

PropertyName 是: num. Value 是: 0 Bean 是: test.annotation.validator.Address@197d257

BeanClass 是:class test.annotation.validator.Address

InvalidValue 的长度是:4 . 验证消息是: 长度必须介于 3 与 8 之间 . PropertyPath 是:address.street .

PropertyName 是: street. Value 是: 1 Bean 是: test.annotation.validator.Address@197d257

BeanClass 是:class test.annotation.validator.Address


可以看出不满足约束的值都被指出了.

同时该句: ClassValidator<Person> classValidator = new ClassValidator<Person> (Person.class);

我们只验证了 Person. 在Person里面的Address的属性 由于有@Valid Annotations 所以 Address的相关属性也被机联验证了 .



如果 把@Valid Annotations 去掉,结果如下:

InvalidValue 的长度是:2 . 验证消息是: 必须大于等于 1 . PropertyPath 是:age .

PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@18fef3d

BeanClass 是:class test.annotation.validator.Person

InvalidValue 的长度是:2 . 验证消息是: 长度必须介于 4 与 2147483647 之间 . PropertyPath 是:name .

PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@18fef3d

BeanClass 是:class test.annotation.validator.Person

可以看出 没有验证 Address.



当然了 ,你还可以只验证一个属性 , 没有必要验证整个类.只需要在调用classValidator.getInvalidValues(p,"age")方法时 加上你要验证的属性就可以了.如我们只想验证age 属性 把代码改为如下所示:

InvalidValue[] validMessages = classValidator.getInvalidValues(p,"age"); //只验证age 属性

运行结果如下:

InvalidValue 的长度是:1 . 验证消息是: 必须大于等于 1 . PropertyPath 是:age .

PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@1457cb

BeanClass 是:class test.annotation.validator.Person

只是验证了 age 属性.

怎么样 ,很简单吧. 关于 Hibernate Validator 内建的验证Annotations 大家可以看看 API 或者 参考文档(中文版我正在翻译中 请访问我的 Blog 获得最新信息).

自定义约束:

如果你要写自己的约束呢 , 你不用担心 ,这也是很容易的.

任何约束有两部分组成: [约束描述符 即注释]the constraint descriptor (the annotation) 和[约束validator 即 实现类] the constraint validator (the implementation class).

下面我们扩展Hibernate Test suit 中的一个Test 来讲解一下.

首先: 要声明一个constraint descriptor .如下:

package test.annotation.validator;

import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;

import org.hibernate.validator.ValidatorClass;

/**
 * Dummy sample of a bean-level validation annotation
 *
 * @author Emmanuel Bernard
 */
@ValidatorClass(SerializabilityValidator.class)
@Target({METHOD,FIELD,TYPE})
@Retention(RUNTIME)
@Documented
public @interface Serializability {
  int num() default 11; 
  String message() default "bean must be serialiable";
} 
@ValidatorClass(SerializabilityValidator.class) 指出了 constraint validator 类.

@Target({METHOD,FIELD,TYPE})
@Retention(RUNTIME)
@Documented

这几个我就不用解释了吧.

Serializability 里面声明了一个 message 显示约束的提示信息. num 只是为了说明一个方面 在这里面没有实际用途用 .

然后就是 实现一个constraint validator 类 该类要实现Validator<ConstraintAnnotation>.这里是SerializabilityValidator.java 如下:

//$Id: SerializabilityValidator.java,v 1.3 2005/11/17 18:12:11 epbernard Exp $
package test.annotation.validator;

import java.io.Serializable;

import org.hibernate.validator.Validator;

/**
 * Sample of a bean-level validator
 *
 * @author Emmanuel Bernard
 */
public class SerializabilityValidator implements Validator<Serializability>, Serializable {
  public boolean isValid(Object value) {
   //这里只是Validator 里面的 实现验证规则的 方法. value 是要验证的值.
    System.out.println("IN SerializabilityValidator isValid:"+value.getClass()+": " +value.toString()); 
    return value instanceof Serializable;
  }

  public void initialize(Serializability parameters) {
    // 在这里可以 取得 constraint descriptor 里面的属性 如上面我们声明的 num 
    System.out.println("IN SerializabilityValidator: parameters:"+ parameters.num() );
  }
} 

然后在你的类中应用@Serializability  就可以约束一个类实现Serializable 接口了. 如下:

在我们的Person.java类 添加@Serializability  Annotations ,把Person.java 中的 //@Serializability //测试自定义约束 注释去掉就ok了.

运行结果如下:

InvalidValue 的长度是:3 . 验证消息是: bean must be serialiable . PropertyPath 是:null .

PropertyName 是: null. Value 是: test.annotation.validator.Person@1a73d3c Bean 是: test.annotation.validator.Person@1a73d3c

BeanClass 是:class test.annotation.validator.Person

现在把Person类实现 java.io.Serializable 接口 则没有出现 验证错误消息.

消息的国际化也是很简单的,把Serializability  中的message 改为以{}扩住的 属性文件的Key就可以了

public @interface Serializability {
  int num() default 11; 
  String message() default "{Serializable}"; //"bean must be serialiable"; //消息的国际化
}

然 后编辑资料文件. 注意 该资源文件中要包括 Hibernate Validator 内建的资源. 可以在该org\hibernate\validator\resources 包里面的资源文件基础上修改 ,在打包里面 这样就可以了. 自己打包可能不太方便.你可以把该包里面的文件复制出来.然后放到你自己的项目包下在自己编辑, 该测试中 我是放在 test\resources 包下的.

然后在 资源文件中添加 Serializable = '''''' 这么一行, 样例如下:

#DefaultValidatorMessages.properties (DefaultValidatorMessages_zh.properties 不再列出^_^)


#下面是 Hibernate Validator 内建的国际化消息

validator.assertFalse=assertion failed

validator.assertTrue=assertion failed

validator.future=must be a future date

validator.length=length must be between {min} and {max}

validator.max=must be less than or equal to {value}

validator.min=must be greater than or equal to {value}

validator.notNull=may not be null

validator.past=must be a past date

validator.pattern=must match "{regex}"

validator.range=must be between {min} and {max}

validator.size=size must be between {min} and {max}

#下面是自定义的消息

Serializable=Bean not Serializable  //加上自己定义的国际化消息.

在构造ClassValidator 时要添上 资源文件 如下:(在测试类中)

ClassValidator<Person> classValidator = new ClassValidator<Person> (Person.class,ResourceBundle.getBundle("test.resources.DefaultValidatorMessages"));// 加载资源

这样就可以了 .  当然 你还可以 更改 Hibernate Validator 的消息(不是在上面的资源文件中直接修改validator.length = ... 等等 ) , 还记得 Validator 注释中有个 message 元素吗? 你以前用的都是默认值,现在你可以该为你自己定义的了.如:validator.length 我把他改为 "该字符串的长度不符合规定范围范围". 在资源文件中添加一行键值属性对(key定义为 "myMsg")如下:

myMsg=该字符串的长度不符合规定范围范围

并且还要在@Length 注释中提供message的引用的key 如下@Length(min = 4,message = "{myMsg}")

再一次运行测试 ,我们就可以看到上面两条自定义绑定的消息了 .如下:

InvalidValue 的长度是:3 . 验证消息是: Bean 不是 可 Serializable . PropertyPath 是:null .
PropertyName 是: null. Value 是: test.annotation.validator.Person@1bd4722 Bean 是: test.annotation.validator.Person@1bd4722
BeanClass 是:class test.annotation.validator.Person


InvalidValue 的长度是:3 . 验证消息是: 该字符串的长度不符合规定范围范围 . PropertyPath 是:name .
PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@1bd4722
BeanClass 是:class test.annotation.validator.Person

怎么样,比你想象的简单吧.

OK 上面我们讨论了 Hibernate Validator 的主要用法: 但是 该框架有什么用呢? ^_^

内置的验证约束注解

内置的验证约束注解如下表所示(摘自hibernate validator reference):

验证注解

验证的数据类型

说明

@AssertFalse

Boolean,boolean

验证注解的元素值是false

@AssertTrue

Boolean,boolean

验证注解的元素值是true

@NotNull

任意类型

验证注解的元素值不是null

@Null

任意类型

验证注解的元素值是null

@Min(value=值)

BigDecimal,BigInteger, byte,

short, int, long,等任何Number或CharSequence(存储的是数字)子类型

验证注解的元素值大于等于@Min指定的value值

@Max(value=值)

和@Min要求一样

验证注解的元素值小于等于@Max指定的value值

@DecimalMin(value=值)

和@Min要求一样

验证注解的元素值大于等于@ DecimalMin指定的value值

@DecimalMax(value=值)

和@Min要求一样

验证注解的元素值小于等于@ DecimalMax指定的value值

@Digits(integer=整数位数, fraction=小数位数)

和@Min要求一样

验证注解的元素值的整数位数和小数位数上限

@Size(min=下限, max=上限)

字符串、Collection、Map、数组等

验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小

@Past

java.util.Date,

java.util.Calendar;

Joda Time类库的日期类型

验证注解的元素值(日期类型)比当前时间早

@Future

与@Past要求一样

验证注解的元素值(日期类型)比当前时间晚

@NotBlank

CharSequence子类型

验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格

@Length(min=下限, max=上限)

CharSequence子类型

验证注解的元素值长度在min和max区间内

@NotEmpty

CharSequence子类型、Collection、Map、数组

验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)

@Range(min=最小值, max=最大值)

BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型

验证注解的元素值在最小值和最大值之间

@Email(regexp=正则表达式,

flag=标志的模式)

CharSequence子类型(如String)

验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式

@Pattern(regexp=正则表达式,

flag=标志的模式)

String,任何CharSequence的子类型

验证注解的元素值与指定的正则表达式匹配

@Valid

任何非原子类型

指定递归验证关联的对象;

如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证

 

此处只列出Hibernate Validator提供的大部分验证约束注解,请参考hibernate validator官方文档了解其他验证约束注解和进行自定义的验证约束注解定义。



看到这里其实不用我在多说了 大家都知道怎么用,什么时候用. 作为一篇介绍性文章我还是在此给出一个最常用的例子吧,更好的使用方式大家慢慢挖掘吧.

比 如 : 你现在在开发一个人力资源(HR)系统 (其实是我们ERP课程的一个作业 ^_^), 里面要处理大量的数据,尤其是在输入各种资料时 如 登记员工信息. 如果你公司的员工的年龄要求是18 -- 60 那么你所输入的年龄就不能超出这个范围. 你可能会说这很容易啊 , 不用Validator就可以解决啊.这保持数据前验证就可以啦 如if ( e.getAge() > 60 || e.getAge() < 18 ) ........ 给出错误信息 然后提示重新输入不就OK啦 用得着 兴师动众的来个第三方框架吗?

是啊 当就验证这一个属性时, 没有必要啊 ! 但是一个真正的HR 系统,会只有一个属性要验证吗? 恐怕要有N多吧

你要是每一个都那样 写一段验证代码 是不是很烦啊 ,况且也不方便代码重用. 现在考虑一些 Validator 是不是更高效啊,拦截到 约束违例的 属性 就可以直接得到 国际化的消息 可以把该消息显示到一个弹出对话框上 提示更正  !

Validator的用处不只这一种 ,你可以想到如何用呢 ! 欢迎发表你的高见!!

OK 到此 我们的 Hibernate Validator 之旅就要先告一段落了 . 希望这是令你心旷神怡的一次寒冬之旅,

把你学到的应用到你的项目中吧,一定会提高你的生产率的. 相信我 ,没错的  ^_^ !


解析 Hibernate Validator

icess 发表于 2006-01-19
点击数:427 评论数:0 评价:5/1
关键词:Hibernate validator

摘要:

Hibernate Validator 可以是一个独立的验证框架, 所以看完这篇分析 你可以把她独立出来作为你的个人验证框架来使用了 ^_^(如果你有兴趣和时间的话). Hibernate Validator 框架里面有两个主要的类: ClassValidator 和InvalidValue 还有一个接口Validator,在这三个主要的构件?最主要的就只有一个 那就是ClassValidator.另外两个是很好理解的..

文章工具

在前一篇文章 < Hibernate Validator 简介 > http://www.matrix.org.cn/resource/article/44/44153_Hibernate%20Validator%20.html中,我们看到了Hibernate Validator的使用方法,和自定义验证Annotation的实现以及错误消息的国际化等常见问题.

任何获得Matrix授权的网站,转载请保留以下作者信息和链接:
作者:icess(作者的blog:http://blog.matrix.org.cn/page/icess)
关键字:Hibernate Validator

在使用如此优雅的属性验证框架的同时,你是否想了解她的细节呢?她究竟是怎么实现的呢? 那么现在就跟随我来探探她的内核吧!

Hibernate Validator 可以是一个独立的验证框架, 所以看完这篇分析 你可以把她独立出来作为你的个人验证框架来使用了 ^_^(如果你有兴趣和时间的话). Hibernate Validator 框架里面有两个主要的类: ClassValidator 和InvalidValue 还有一个接口Validator,在这三个主要的构件中 最主要的就只有一个 那就是ClassValidator.另外两个是很好理解的..

现在就让我们开始吧. 遵循由浅入深的习惯 我们先看看 Validator 接口吧. 其代码如下:

import java.lang.annotation.Annotation;

/**
* A constraint validator for a particular annotation
*
* @author Gavin King
*/
public interface Validator<A extends Annotation> {
  /**
   * does the object/element pass the constraints
   */
  public boolean isValid(Object value);

  /**
   * Take the annotations values
   * @param parameters
   */
  public void initialize(A parameters);
}



Validator接口就是我们自定义约束的实现类要继承的接口,该接口在< Hibernate Validator 简介 > http://www.matrix.org.cn/resource/article/44/44153_Hibernate%20Validator%20.html中已经讨论过了,请参考.

InvalidValue 类 大家看名字就应该可以猜到她的作用了吧. 她就是代表一个没有通过验证的错误实例.该类定义了一些方法,通过这些方法你可以取得与该Validator Annotation 有关的一些参数,如:她所注释的属性的值,错误消息等等. 该类的源代码如下:

import java.io.Serializable;

/**
* A single violation of a class level or method level constraint.
*
* @author Gavin King
*/
public class InvalidValue implements Serializable {
  private final String message;
  private final Object value;
  private final String propertyName;
  private final Class beanClass;
  private final Object bean;
  private Object rootBean;

  public Object getRootBean() {
    return rootBean;
  }

  public String getPropertyPath() {
    return propertyPath;
  }

  private String propertyPath;

  public InvalidValue(String message, Class beanClass, String propertyName, Object value, Object bean) {
    this.message = message;
    this.value = value;
    this.beanClass = beanClass;
    this.propertyName = propertyName;
    this.bean = bean;
    this.rootBean = bean;
    this.propertyPath = propertyName;
  }

  public void addParentBean(Object parentBean, String propertyName) {
    this.rootBean = parentBean;
    this.propertyPath = propertyName + "." + this.propertyPath;
  }

  public Class getBeanClass() {
    return beanClass;
  }

  public String getMessage() {
    return message;
  }

  public String getPropertyName() {
    return propertyName;
  }

  public Object getValue() {
    return value;
  }

  public Object getBean() {
    return bean;
  }

  public String toString() {
    return propertyName + ' ' + message;
  }

}



然后,就让我们看看最主要的类吧:ClassValidator . 该类代码有400余行,我都做了详细的注释如下:

import 该部分省略了;


/**
* Engine that take a bean and check every expressed annotation restrictions
*
* @author Gavin King
*/
public class ClassValidator<T> implements Serializable {
  private static Log log = LogFactory.getLog( ClassValidator.class );
  private static final InvalidValue[] EMPTY_INVALID_VALUE_ARRAY = new InvalidValue[]{};
  private final Class<T> beanClass;
  private transient ResourceBundle messageBundle;
  private transient boolean defaultResourceBundle;

  private final transient Map<Class, ClassValidator> childClassValidators;
  private transient List<Validator> beanValidators;
  private transient List<Validator> memberValidators;
  private transient List<Member> memberGetters;
  private transient Map<Validator, String> messages;
  private transient List<Member> childGetters;
  private static final String DEFAULT_VALIDATOR_MESSAGE = "org.hibernate.validator.resources.DefaultValidatorMessages";


  /**
   * create the validator engine for this bean type
   */
  public ClassValidator(Class<T> beanClass) {
    this( beanClass, null );
  }

  /**
   * create the validator engine for a particular bean class, using a resource bundle
   * for message rendering on violation
   */
  public ClassValidator(Class<T> beanClass, ResourceBundle resourceBundle) {
    this( beanClass, resourceBundle, new HashMap<Class, ClassValidator>() );
  }

  protected ClassValidator(
      Class<T> beanClass, ResourceBundle resourceBundle, Map<Class, ClassValidator> childClassValidators
  ) {
    this.beanClass = beanClass;
    this.messageBundle = resourceBundle == null ?
        getDefaultResourceBundle() :
        resourceBundle;
    this.childClassValidators = childClassValidators;
    initValidator( beanClass, childClassValidators, this.messageBundle );  //重要的是该初始化函数
  }

  private ResourceBundle getDefaultResourceBundle() {
    ResourceBundle rb;
    try {
      rb = ResourceBundle.getBundle( "ValidatorMessages" );
    }
    catch( MissingResourceException e) {
      //the user did not override the default ValidatorMessages
      log.debug( "ResourceBundle ValidatorMessages not found. Delegate to " + DEFAULT_VALIDATOR_MESSAGE);
      rb = ResourceBundle.getBundle( DEFAULT_VALIDATOR_MESSAGE );
    }
    defaultResourceBundle = true;
    return rb;
  }

  private void initValidator(
      Class<T> beanClass, Map<Class, ClassValidator> childClassValidators,
      ResourceBundle resourceBundle
  ) {
    beanValidators = new ArrayList<Validator>(); // 保存类级别的验证约束实现类
    memberValidators = new ArrayList<Validator>(); // 保存方法级别的验证约束实现类
    memberGetters = new ArrayList<Member>();// 保存类的成员(字段or方法)和构造函数方法的标识信息
    messages = new HashMap<Validator, String>(); // 利用Map保存与每个Validator相对应的验证消息
    childGetters = new ArrayList<Member>();//  保存子类的成员(字段or方法)和构造函数方法的标识信息

    childClassValidators.put( beanClass, this ); //map Map<Class, ClassValidator> childClassValidators;
    Annotation[] classAnnotations = beanClass.getAnnotations();
    for ( int i = 0; i < classAnnotations.length ; i++ ) {
      Annotation classAnnotation = classAnnotations[i];
     Validator beanValidator = createValidator( classAnnotation );//根据Annotation来得到Validator,参考对该函数的解释
      if ( beanValidator != null ) beanValidators.add( beanValidator );//保存该Validator
    }
    //build the class hierarchy to look for members in
    Collection<Class> classes = new HashSet<Class>();
    addSuperClassesAndInterfaces( beanClass, classes );//把beanClass的所有超类和实现的接口添加的集合classes中

    //Check on all selected classes
    for ( Class currClass : classes ) {
      Method[] methods = currClass.getDeclaredMethods();// 扫描Method上面的注释
      for ( int i = 0; i < methods.length ; i++ ) {
        Method method = methods[i];
        createMemberValidator( method ); // 创建方法上的约束实现类(Validator), 参考对该函数的解释
        Class clazz = method.getReturnType();// 得到该方法的返回类型
        createChildValidator( resourceBundle, method, clazz );// 创建子类的Validator
      }

      Field[] fields = currClass.getDeclaredFields(); // 扫描Field上面的注释, 下面和上面Method的实现一样
      for ( int i = 0; i < fields.length ; i++ ) {
        Field field = fields[i];
        createMemberValidator( field );
        Class clazz = field.getType();
        createChildValidator( resourceBundle, field, clazz );
      }
    }
  }

  private void addSuperClassesAndInterfaces(Class clazz, Collection<Class> classes) {
    for ( Class currClass = clazz; currClass != null ; currClass = currClass.getSuperclass() ) {
      if ( ! classes.add( currClass ) ) return;
      Class[] interfaces = currClass.getInterfaces();
      for (Class interf : interfaces) {
        addSuperClassesAndInterfaces( interf, classes );
      }
    }
  }

  /**
   * 创建内嵌类的Validator. 如果该内嵌类被Valid Annotation 注释的话则
   * 创建另外一个ClassValidator
   * @param resourceBundle
   * @param member
   * @param clazz
   */
  private void createChildValidator(ResourceBundle resourceBundle, Member member, Class clazz) {
    if ( ( (AnnotatedElement) member ).isAnnotationPresent( Valid.class ) ) {
      setAccessible( member );
      childGetters.add( member );
      if ( !childClassValidators.containsKey( clazz ) ) {
        new ClassValidator( clazz, resourceBundle, childClassValidators );
      }
    }
  }

  /**
   * 利用传入的Method(实现了AnnotatedElement, GenericDeclaration, Member接口)
   * 得到 方法上的Annotations 然后利用私有方法createValidator(Annotation a)来创建
   * 每一个Annotation 的实现类 Validator 并保存Validator和member
   * @param member
   */
  private void createMemberValidator(Member member) {
    Annotation[] memberAnnotations = ( (AnnotatedElement) member ).getAnnotations();
    for ( int j = 0; j < memberAnnotations.length ; j++ ) {
      Annotation methodAnnotation = memberAnnotations[j];
      Validator propertyValidator = createValidator( methodAnnotation );
      if ( propertyValidator != null ) {
        memberValidators.add( propertyValidator );
        setAccessible( member ); // 设置访问属性
        memberGetters.add( member );
      }
    }
  }

  private static void setAccessible(Member member) {
    if ( !Modifier.isPublic( member.getModifiers() ) ) {
      ( (AccessibleObject) member ).setAccessible( true );
    }
  }

  /**
   * 该方法产生了该Annotation的约束实现类 并初始化该类对应的消息
   */
  private Validator createValidator(Annotation annotation) {
    try {
      //得到ValidatorClass Annotation
      ValidatorClass validatorClass = annotation.annotationType().getAnnotation( ValidatorClass.class );
      if ( validatorClass == null ) {
        return null;
      }
      // 然后 利用ValidatorClass Annotation 来得到里面的值(即实现该注释的Class),
      //再利用Class 构造一个instance
      Validator beanValidator = validatorClass.value().newInstance();
      beanValidator.initialize( annotation ); // 初始化Annotation中的参数(注意:在自定义约束中该方法有你来实现)
      String messageTemplate = (String) annotation.getClass()
          .getMethod( "message", (Class[]) null )
          .invoke( annotation );  // 取得 constraint descriptor  中的message 的值
      String message = replace( messageTemplate, annotation ); // 初始化取得的模板消息 请参考 replace函数
      messages.put( beanValidator, message ); // 把message 放在map中,以便使用
      return beanValidator; // 返回 产生的Validator
    }
    catch (Exception e) {
      throw new IllegalArgumentException( "could not instantiate ClassValidator", e );
    }
  }

  public boolean hasValidationRules() {
    return beanValidators.size() != 0 || memberValidators.size() != 0;
  }

  /**
   * apply constraints on a bean instance and return all the failures.
   */
  public InvalidValue[] getInvalidValues(T bean) {
    return this.getInvalidValues( bean, new IdentitySet() );
  }

  /**
   * apply constraints on a bean instance and return all the failures.
   */
  protected InvalidValue[] getInvalidValues(T bean, Set<Object> circularityState) {
    if ( circularityState.contains( bean ) ) {  // 该if else 是和Hibernate Core由关的,
      return EMPTY_INVALID_VALUE_ARRAY; //Avoid circularity
    }
    else {
      circularityState.add( bean );
    }

    if ( !beanClass.isInstance( bean ) ) { // 如果beanClass不是该bean的实例,则抛出异常
      throw new IllegalArgumentException( "not an instance of: " + bean.getClass() );
    }

    List<InvalidValue> results = new ArrayList<InvalidValue>();

    for ( int i = 0; i < beanValidators.size() ; i++ ) { // 验证类级别的约束
      Validator validator = beanValidators.get( i );
      if ( !validator.isValid( bean ) ) { //调用isValid方法,如果没有通过则添加到list<InvalidValue>中
                        //如果是自定义约束则isValid方法 由你来实现
        results.add( new InvalidValue( messages.get( validator ), beanClass, null, bean, bean ) );
      }
    }

    for ( int i = 0; i < memberValidators.size() ; i++ ) {//验证方法级别的约束
      Member getter = memberGetters.get( i );
      if ( Hibernate.isPropertyInitialized(bean, getter.getName() ) ) {// ? 检查该属性是否已初始化
        Object value = getMemberValue( bean, getter );//利用反射 取得该属性的值
        Validator validator = memberValidators.get( i ); //取得该约束的验证实现类
        if ( !validator.isValid( value ) ) {//调用isValid方法,如果没有通过则添加到list<InvalidValue>中
          String propertyName = getPropertyName( getter );
          results.add( new InvalidValue( messages.get( validator ), beanClass, propertyName, value, bean ) );
        }
      }
    }

    for ( int i = 0; i < childGetters.size() ; i++ ) {// 处理子类类
      Member getter = childGetters.get( i );
      if ( Hibernate.isPropertyInitialized(bean, getter.getName() ) ) { //检查该属性是否已初始化
        Object value = getMemberValue( bean, getter );
        if ( value != null && Hibernate.isInitialized( value ) ) {
          String propertyName = getPropertyName( getter );
          InvalidValue[] invalidValues = getClassValidator( value )
              .getInvalidValues( value, circularityState );// 通过参数value 得到 Class, 然后由Class作为key          //在childClassValidators map中得到其ClassValidator
                                     //如果不存在 则创建新的 ,然后再调用ClassValidator的getInvalidValues方法
          // 注意在调用getInvalidValues方法时 用到了circularityState 参数, 当调用循环一周时 返回(递归结束)
          for ( InvalidValue invalidValue : invalidValues ) {
            invalidValue.addParentBean( bean, propertyName );
            results.add( invalidValue ); //添加的结果中
          }
        }
      }
    }

    return results.toArray( new InvalidValue[results.size()] ); //返回InvalidValue数组
  }

  /**
   * 通过参数value 得到 Class, 然后由Class作为key 在childClassValidators map中得到其ClassValidator
   * 如果不存在 则创建新的 然后返回
   * @param value
   * @return
   */
  private ClassValidator getClassValidator(Object value) {
    Class clazz = value.getClass();
    ClassValidator validator = childClassValidators.get( clazz );
    if ( validator == null ) { //handles polymorphism
      validator = new ClassValidator( clazz );
    }
    return validator;
  }

  /**
   * Apply constraints of a particular property on a bean instance and return all the failures.
   * Note this is not recursive.
   * 验证单个属性的约束.
   */
  //TODO should it be recursive ?
  public InvalidValue[] getInvalidValues(T bean, String propertyName) {
    List<InvalidValue> results = new ArrayList<InvalidValue>();

    for ( int i = 0; i < memberValidators.size() ; i++ ) {
      Member getter = memberGetters.get( i );
      if ( getPropertyName( getter ).equals( propertyName ) ) {// 验证该属性的约束
        Object value = getMemberValue( bean, getter );
        Validator validator = memberValidators.get( i );
        if ( !validator.isValid( value ) ) {
          results.add( new InvalidValue( messages.get( validator ), beanClass, propertyName, value, bean ) );
        }
      }
    }

    return results.toArray( new InvalidValue[results.size()] );
  }

  /**
   * Apply constraints of a particular property value of a bean type and return all the failures.
   * The InvalidValue objects returns return null for InvalidValue#getBean() and InvalidValue#getRootBean()
   * Note this is not recursive.
   * 验证 value 是否满足当前属性的约束.
   */
  //TODO should it be recursive?
  public InvalidValue[] getPotentialInvalidValues(String propertyName, Object value) {
    List<InvalidValue> results = new ArrayList<InvalidValue>();

    for ( int i = 0; i < memberValidators.size() ; i++ ) {
      Member getter = memberGetters.get( i );
      if ( getPropertyName( getter ).equals( propertyName ) ) {
        Validator validator = memberValidators.get( i );
        if ( !validator.isValid( value ) ) {
          results.add( new InvalidValue( messages.get( validator ), beanClass, propertyName, value, null ) );
        }
      }
    }

    return results.toArray( new InvalidValue[results.size()] );
  }

  private Object getMemberValue(T bean, Member getter) {
    Object value;
    try {
      value = getValue( getter, bean );
    }
    catch (Exception e) {
      throw new IllegalStateException( "Could not get property value", e );
    }
    return value;
  }

  private Object getValue(Member member, T bean) throws IllegalAccessException, InvocationTargetException {
    if ( member instanceof Field ) {
      return ( (Field) member ).get( bean );
    }
    else if ( member instanceof Method ) {
      return ( (Method) member ).invoke( bean );
    }
    else {
      throw new AssertionFailure( "Unexpected member: " + member.getClass().getName() );
    }
  }

  public String getPropertyName(Member member) {
    //Do no try to cache the result in a map, it's actually much slower (2.x time)
    String propertyName;
    if ( member instanceof Field ) {
      propertyName = member.getName();
    }
    else if ( member instanceof Method ) {
      propertyName = member.getName();
      if ( propertyName.startsWith( "is" ) ) {
        propertyName = Introspector.decapitalize( propertyName.substring( 2 ) );
      }
      else if ( propertyName.startsWith( "get" ) ) {
        propertyName = Introspector.decapitalize( propertyName.substring( 3 ) );
      }
      //do nothing for non getter method, in case someone want to validate a PO Method
    }
    else {
      throw new AssertionFailure( "Unexpected member: " + member.getClass().getName() );
    }
    return propertyName;
  }

  private String replace(String message, Annotation parameters) {
    StringTokenizer tokens = new StringTokenizer( message, "{}", true );
    StringBuilder buf = new StringBuilder( 30 );
    boolean escaped = false;
    while ( tokens.hasMoreTokens() ) {
      String token = tokens.nextToken();
      if ( "{".equals( token ) ) {
        escaped = true;
      }
      else if ( "}".equals( token ) ) {
        escaped = false;
      }
      else if ( !escaped ) {
        buf.append( token );
      }
      else {
        Method member;
        try {
          member = parameters.getClass().getMethod( token, (Class[]) null );
        }
        catch (NoSuchMethodException nsfme) {
          member = null;
        }
        if ( member != null ) {
          try {
            buf.append( member.invoke( parameters ) );
          }
          catch (Exception e) {
            throw new IllegalArgumentException( "could not render message", e );
          }
        }
        else if ( messageBundle != null ) {
          String string = messageBundle.getString( token );
          if ( string != null ) buf.append( replace( string, parameters ) );
        }
      }
    }
    return buf.toString();
  }

  /**
   * apply the registred constraints rules on the hibernate metadata (to be applied on DB schema...)
   *该方法是与实体类绑定的 不推荐使用 有兴趣的读者可以自己研究一下
   * @param persistentClass hibernate metadata
   */
  public void apply(PersistentClass persistentClass) {  
   //源代码省略
  }

  /**
   * 断言该bean 是否符合所有约束. 负责抛出异常
   * @param bean
   */
  public void assertValid(T bean) {
    InvalidValue[] values = getInvalidValues( bean );
    if ( values.length > 0 ) {
      throw new InvalidStateException( values );
    }
  }

  /**
   * 该方法应该是序列化ResourceBundle的 为private方法 但并没有用到, 不知道为什么 可能以后会有用
   * @param oos
   * @throws IOException
   */
  private void writeObject(ObjectOutputStream oos) throws IOException {
    ResourceBundle rb = messageBundle;
    if ( rb != null && ! ( rb instanceof Serializable ) ) {
      messageBundle = null;
      if ( ! defaultResourceBundle )
        log.warn( "Serializing a ClassValidator with a not serializable ResourceBundle: ResourceBundle ignored" );
    }
    oos.defaultWriteObject();
    oos.writeObject( messageBundle );
    messageBundle = rb;
  }

  /**
   * 该方法应该是读取序列化的ResourceBundle的 为private方法 但并没有用到,不知道为什么 可能以后会有用
   * @param ois
   * @throws IOException
   * @throws ClassNotFoundException
   */
  private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ois.defaultReadObject();
    ResourceBundle rb = (ResourceBundle) ois.readObject();
    if (rb == null) rb = getDefaultResourceBundle();
    initValidator( beanClass, new HashMap<Class, ClassValidator>(), rb );
  }
}



还记得我们在验证时候所写的代码吗:

 ClassValidator<Person> classValidator = new ClassValidator<Person> (Person.class);
InvalidValue[] validMessages = classValidator.getInvalidValues(p);



只调用了classValidator的getInvalidValues(p);方法 我们就得到了InvalidValue[] validMessages, 该方法做了什么事情呢? 有上面的注释看起来就轻松多了 ^_^.

首先:在你创建ClassValidator时, 会调用ClassValidator的构造方法 她一供有三个构造函数 :
  

有两个构造函数(一个传递要验证的类为参数,一个还要加上你自定义的ResourceBundle)来供我们使用, 还有一个protected 的构造函数. 在构造函数中都做了写什么呢?

第一: 把要验证的类保存起来,第二:决定使用的消息资源,如果你提供了自己的ResourceBundle 就使用自定义消息,否则使用默认的消息资源.第三: 根据java反射机制,利用Annotation初始化所有的验证约束类,然后验证是否满足验证条件.

下面我们来关注一下initValidator 方法,看看是如何初始化验证约束类的. 现在请仔细看看 initValidator 里面的注释.然后在继续往下看.^_^

通 过上面的分析,可以看到在 initValidator函数中,初始化了你传入类的所有的约束Annotations 的相关的东东(如: 其约束验证实现类, 如果有内嵌的类,如果该类被Valid Annotation注释的话 也构造一个内嵌类的Validator 并初始化其相关的东东 如此递归执行下去).该函数执行完后,可以说构造了一个以你传入的类为跟的 约束注释树(自创的名词,不了解也没关系 ^_^),然后由此树来逐个验证没有个约束.此时已经具备验证约束的条件了.你只有调用 classValidator.getInvalidValues(p)方法就可以验证类p 上的所有约束了.

GetInvalidValues()方法有做了什么呢, 现在你要再回到上面看看她的注释了  ^_^:

怎 么样现在知道 GetInvalidValues 做了什么了吧.她就是取出 约束注释树中的每一个约束注释(分为 类注释, 方法注释, 属性注释 和内嵌类注释 (也就是类里面的类属性)),并验证相应的成员是否满足该约束注释的要求,也就是调用Validator的isValid() 方法.最后用不满足要求的 成员信息构造InvalidValue 数组 并返. ClassValidator 类我们基本上已经讲解完了,剩下的该Validatro里面的就是一些内建的约束Annotation和约束验证实现类了,这些看看前一篇文章就明白怎么 回事了.到此 HibernateValidator 框架基本上分析完了. 通过分析该框架.让我看到了Annotation的一种高级用法的实现机制,和反射机制的巧妙应用,以及几个巧妙的设计模式(我就不在举例了 大家可以相互探讨一下). 你从中学到了什么呢?

对想把Hibernate Validator做成一个独立框架的几点说明:

1.去掉apply 函数.

2. 在getPropertyName 和 getMemberValue 中 如果得到的值为null 则抛出org.hibernate.AssertionFailure异常. 可以重写该异常,或者从Hibernate源代码中提取(建议重写).

3. 用到了Hibernate.isPropertyInitialized(Object o,String name)方法 判断该类(o)的属性(name)是否以及加载的, 该函数的doc 注释为 Check if the property is initialized. If the named property does not exist or is not persistent, this method always returns true.可以替换为判断该属性(name)是否为null, null即代表没有赋初值(可能违反约束);否则验证该值是否违反约束.

4.里面还用到了org.hibernate.util.IdentitySet 一个set实现,可以自己实现或者从Hibernate中提取(推荐提取);

这样一个独立的Validation frameWork 就出来了. 不依赖任何第三方代码,完全可以作为你自己的验证框架在项目中使用.

posted on 2015-12-03 21:54  duanxz  阅读(3341)  评论(0编辑  收藏  举报