Hibernate验证器工作在两个层次上。第一层,它能检查内存中一个类的实例是否违反约束。 第二层,它能将约束应用于Hibernate元模型上,并将它们融入生成的数据库schema中。
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,则递归验证其值元素。 | 无 |
属性(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() { ... }; }
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");