添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

注解是一种为领域模型(domain model)指定不变约束的简洁而幽雅的方法。例如,你能 表示一个属性永远不为null,一个帐户余额一定是正值,等等。这些域模型约束通过为bean中的属性添加 注解来加以声明。随后一个验证器(validator)会读取并检查这些约束。验证机制可以执行于应用程序中的 不同层(表现层、数据访问层),而不必复述任何(前述)这些规则。Hibernate验证器正为这一目的而设计的。

Hibernate验证器工作在两个层次上。第一层,它能检查内存中一个类的实例是否违反约束。 第二层,它能将约束应用于Hibernate元模型上,并将它们融入生成的数据库schema中。

每个约束注解(constraint annotation)和一个验证器实现关联,该验证器负责检查位于实体实例上的约束。 一个验证器也能(可选地)将约束应用于Hibernate元模型上,让Hibernate生成表示这一约束的DDL。使用合适的事件监听器,你能 让Hibernate在插入和更新时执行检查操作。Hibernate验证器并不局限于同Hibernate一起使用。 你能在你应用程序的任何地方方便地使用它。

在运行时检查实例时,Hibernate验证器返回违反约束的信息, 这些信息以一个 InvalidValue 数组的形式返回。 除了众多其他信息外, InvalidValue 包含了一个错误描述消 息,该信息可以内嵌与注解相捆绑的参数值(例如长度限制),以及能被提取至ResourceBundle的消息字串。

Hibernate验证器有些内建约束,这些约束覆盖了大多数的基本数据检查。随后我们会看到, 你不必受制于这些内置约束,因为一分钟内就可以写出你自己的约束。

表 4.1. 内建约束

注解 应用目标 运行时检查 Hibernate元数据影响
@Length(min=, max=) 属性(String) 检查字符串长度是否符合范围 列长度会被设到最大值
@Max(value=) 属性 (以numeric或者string类型来表示一个数字) 检查值是否小于或等于最大值 对列增加一个检查约束
@Min(value=) 属性(以numeric或者string类型来表示一个数字) 检查值是否大于或等于最小值 对列增加一个检查约束
@NotNull 属性 检查值是否非空(not null) 列不为空
@Past 属性(date或calendar) 检查日期是否是过去时 对列增加一个检查约束
@Future 属性 (date 或 calendar) 检查日期是否是将来时
@Pattern(regex="regexp", flag=) 属性 (string) 检查属性是否与给定匹配标志的正则表达式相匹配(见 java.util.regex.Pattern )
@Range(min=, max=) 属性(以numeric或者string类型来表示一个数字) 检查值是否在最小和最大值之间(包括临界值) 对列增加一个检查约束
@Size(min=, max=) 属性 (array, collection, map) 检查元素大小是否在最小和最大值之间(包括临界值)
@AssertFalse 属性 检查方法的演算结果是否为false(对以代码方式而不是注解表示的约束很有用)
@AssertTrue 属性 检查方法的演算结果是否为true(对以代码方式而不是注解表示的约束很有用)
@Valid 属性 (object) 对关联对象递归的进行验证。如果对象是集合或数组,就递归地验证其元素。如果对象是Map,则递归验证其值元素。
@Email 属性(String) 检查字符串是否符合有效的email地址规范。

扩展内建约束集是极其方便的。任何约束都包括两部分:约束 描述符 (注解) 和约束 验证器 (实现类)。下面是一个简单的用户定义描述符:

@ValidatorClass(CapitalizedValidator.class)
@Target(METHOD) 
@Retention(RUNTIME)
@Documented
public @interface Capitalized {
    CapitalizeType type() default Capitalize.FIRST;
    String message() default "has incorrect capitalization";
}

type 参数描述属性应该如何被大写。这是一个完全依赖于注解业务(逻辑)的用户 参数。

message 是用于描述约束违规的默认字符串,它是强制要求的。你可以采取硬编码的方式, 或者通过Java ResourceBundle机制将message的部分/全部内容提取至外部文件。一旦发现message中{parameter}字符串, 就会在{parameter}这个位置注入相应的参数值(在我们的例子里Capitalization is not {type}会生成 Capitalization is not FIRST), 可以将message对应的整个字符串提取至外部文件ValidatorMessages.properties,这也是一种良好实践。 见 Error messages

@ValidatorClass(CapitalizedValidator.class)
@Target(METHOD) 
@Retention(RUNTIME)
@Documented
public @interface Capitalized {
    CapitalizeType type() default Capitalize.FIRST;
    String message() default "{validator.capitalized}";
#in ValidatorMessages.properties
validator.capitalized=Capitalization is not {type}

如你所见{}符号是递归的。

为了将一个描述符连接到它的验证器实现,我们使用 @ValidatorClass 元注解。验证器类参数必须指定一个实现了 Validator<ConstraintAnnotation> 的类。

我们现在要实现验证器(也就是实现规则检查)。一个验证器实现能检查一个属性的值 (实现 PropertyConstraint ),并且/或者可以修改hibernate映射元数据 (实现 PersistentClassConstraint ),籍此表示数据库级的约束。

public class CapitalizedValidator 
        implements Validator<Capitalized>, PropertyConstraint {
    private CapitalizeType type;
    //part of the Validator<Annotation> contract, 
    //allows to get and use the annotation values
    public void initialize(Capitalized parameters) {
        type = parameters.type();
    //part of the property constraint contract
    public boolean isValid(Object value) {
        if (value==null) return true;
        if ( !(value instanceof String) ) return false;
        String string = (String) value;
        if (type == CapitalizeType.ALL) {
            return string.equals( string.toUpperCase() );
        else {
            String first = string.substring(0,1);
            return first.equals( first.toUpperCase();
}

如果违反约束, isValid() 方法将返回false。更多例子请参考内建验证器实现。

至此我们只看到属性级的验证,你还可以写一个Bean级别的验证注解。Bean自身会被传递给验证器, 而不是bean的属性实例。只要对bean自身进行注解即可激活验证检查。在单元测试套件中还可以找到一个小例子。

既然你现在已经熟悉注解了,那么对语法也应该很清楚了。

public class Address {
    private String line1;
    private String line2;
    private String zip;
    private String state;
    private String country;
    private long id;
    // a not null string of 20 characters maximum
    @Length(max=20) 
    @NotNull
    public String getCountry() {
        return country;
    // a non null string
    @NotNull
    public String getLine1() {
        return line1;
    //no constraint    
    public String getLine2() {
        return line2;
    // a not null string of 3 characters maximum
    @Length(max=3) @NotNull
    public String getState() {
        return state;
    // a not null numeric string of 5 characters maximum
    // if the string is longer, the message will 
    //be searched in the resource bundle at key 'long'
    @Length(max=5, message="{long}")
    @Pattern(regex="[0-9]+")
    @NotNull
    public String getZip() {
        return zip;
    // should always be true
    @AssertTrue
    public boolean isValid() {
        return true;
    // a numeric between 1 and 2000
    @Id @Min(1)
    @Range(max=2000)
    public long getId() {
        return id;
}

上面的例子只展示了公共属性验证,你还可以对任何可见度的字段(field)进行注解。

@MyBeanConstraint(max=45)
public class Dog {
    @AssertTrue private boolean isMale;
    @NotNull protected String getName() { ... };
}

你可以对接口进行注解。Hibernate验证器会检查给定bean所扩展或实现的所有父类和接口, 籍以读取相应的验证器注解(信息)。

public interface Named {
    @NotNull String getName();
public class Dog implements Named {
    @AssertTrue private boolean isMale;
    public String getName() { ... };
}

在验证Dog bean时会检查name属性的有效性(不为null)。

Hibernate验证器旨在实现多层数据验证,我们在一处表示约束(带注解的域模型),然后将其运用于 应用程序的不同层。

Hibernate验证器能应用于你应用程序代码中的任何地方。

ClassValidator personValidator = new ClassValidator( Person.class );
ClassValidator addressValidator = new ClassValidator( Address.class, ResourceBundle.getBundle("messages", Locale.ENGLISH) );
InvalidValue[] validationMessages = addressValidator.getInvalidValues(address);

头两行为执行类检查而准备Hibernate验证器。第一行依赖于嵌入在Hibernate验证器内的错误 消息(见 Error messages ),第二行为这些消息准备资源包。这些代码只执行一次, 并将验证器进行缓存处理,这种方式是一种良好实践。

第三行真正验证了 Address 实例并返回一个 InvalidValue 数组。 你的应用程序逻辑随后可以对错误做出响应。

除了针对整个bean你还可以对某个特定属性进行检查。这对于一个属性一个属性的用户交互情形或许是有用的。

ClassValidator addressValidator = new ClassValidator( Address.class, ResourceBundle.getBundle("messages", Locale.ENGLISH) );
//only get city property invalid values
InvalidValue[] validationMessages = addressValidator.getInvalidValues(address, "city");