Bean Validation规范定义了一个框架,用于声明对JavaBean类,字段和属性的约束。
约束在类型上声明,并根据实例或示例图进行验证评估
3.1、对要验证的类要求
- 要验证的对象必须满足以下要求:
- 要验证的属性必须遵循JavaBeans规范定义的JavaBeans读取属性的方法签名约定。
- 验证中不包括静态字段和静态方法。
- 约束可以应用于接口和超类
- 注解定义的目标可以是字段,属性或类型,条件是:
- 约束定义支持指定的目标(java.lang.annotation.Target)
- 在约束上声明的ConstraintValidators之一支持目标的声明类型(请参见第3.5.3节)
3.1.1、 对象验证
- 约束声明可以应用于类或接口,将约束应用于类或接口可对类或实现接口的类的状态进行验证。
3.1.2、字段和属性验证
- 约束声明可以应用于同一对象类型的字段和属性。但是,不应在字段及其关联属性之间重复相同的约束(约束验证将验证两次),建议持有约束声明的对象遵循单一状态访问策略(带注解的字段或属性)。
- Java的持久化和Bean验证
- 为了获得最大的移植性,承载Bean验证约束的持久属性应使用Java持久性中使用的相同访问策略。换句话说,将Bean Validation约束注解与Java 持久化注解放在同一元素(字段或getter)上。
- 当使用约束声明对field进行注解时,将使用字段访问策略来访问通过这种约束验证的状态。
- 当使用约束声明对property进行注解时,将使用字段访问策略来访问通过这种约束验证的状态。
- 使用字段访问策略时,bean验证提供程序直接访问实例变量。使用属性访问策略时,bean验证提供程序通过属性访问器方法访问状态。当使用受约束的属性时,对于受约束的属性,要求该类遵循JavaBeans读取属性(由JavaBeans Introspector类定义)的方法签名约定。在这种情况下,对于类型T的每个约束属性,都有一个getter方法get。对于布尔属性,is是getter方法的替代名称。具体来说,如果getX是getter方法的名称。其中X是字符串,则持久属性的名称由java.beans.Introspector.decapitalize(X)的结果定义。
- 字段或方法的可见性不受限制。不支持对非获取方法的约束
3.1.3、图形验证
- 除了支持实例验证之外,还支持对象图的验证。图形验证的结果作为一组统一的约束违规返回。
- 考虑bean X包含类型为Y的字段的情况,通过使用@Valid注解对字段Y进行注解,验证器将在验证X时验证Y(及其属性)。在运行时确定类型Y声明的字段(子类,实现)中包含的值的确切类型Z,使用Z的约束定义。这样可以确保标记为@Valid的关联具体正确的多态行为。
- 集合值,数组值和通常Iterable的字段和属性也可以使用@Valid注解修饰。这将使迭代器的内容得到验证。支持任何实现java.lang.Iterable的对象。具体包括:
- 数组对象
- java.util.Collection
- java.util.Set
- java.util.List
- java.util.Map(特殊对象,详见下文)
- 验证迭代器提供的每个对象,对于Map,每个Map.Entry的值都经过验证(key未经验证)。
- 像常规引用一样,它的类型在运行时确定,并且使用此特定类型的约束定义。
- @Valid注解是递归应用的, 符合标准的实现可以避免按照3.5.1节中所述的规则进行无限循环。
3.2、约束声明
- 约束声明主要通过注解放置在类或接口上。约束注解(请参阅第2.1节)可以应用于类型,任何类型的字段或任何与JavaBeans兼容的属性。
- 在类上定义约束时,验证的类的实例将传递到ConstraintValidator进行验证,在字段上定义约束时,该字段的值将传递到ConstraintValidator.在getter上定义约束时,getter调用的结果将传递到ConstraintValidator.
3.3、继承(接口和父类)
- 约束声明可以放在接口上。对于给定的类,由Bean验证提供程序评估保存在超类以及接口上的约束声明。规则在3.4.5节中正式描述
- 约束声明的作用是累积的,根据Java语言规范可见性规则,将验证在超类getter上声明的约束以及在getter的覆盖版本上定义的所有约束。
3.4、组和组序列
-
一组定义约束的子集。代替验证给定对象图的所有约束,仅验证子集,该子集由目标组定义。每个约束声明定义它所属的组的列表。如果未明确声明任何组,则约束属于默认组。
-
组由接口表示:
-
例子3.1、定义组
-
/** * 验证用户是否可计费的验证组 */ public interface Billable{} /** * 客户可以购买而无需任何麻烦的检查过程 */ public interface BuyInOneClick{}
-
-
一个约束可以属于一个或多个组
-
例子3.2、将组赋值给约束
-
/** * 用户信息 */ public class User{ @NotNull private String firstname; @NotNull(groups=Default.class) private String lastname; @NotNull(groups={Billable.class, BuyInOneClick.class}) private CreditCard defaultCreditCard; }
-
在验证调用时,将验证一个或多个组,在对象图上评估属于该组集合的所有约束。在示例3.2中,当验证了Billable或BuyInOneClick组时,将在defaultCreditCard上选中@NotNull。当验证默认组时,将验证firstname和lastname上的@NotNull 。 温馨提醒:考虑对超类和接口的约束。
-
-
Default 组被预先定义在规范中
-
/** * 默认 Bean验证的默认组 * 除非明确定义组列表,否则: * 约束属于默认组 * 验证也应用默认组 * 大部分结构约束应属于默认组 */ public interface Default{ }
-
3.4.1、组的继承
-
在某些情况下,组是一个或多个组的超集。可以通过Bean验证来描述。一个组可以通过接口继承来继承一个或多个组。
-
例子3.3、组可以继承其他组
-
/** * 客户可以购买而无需任何麻烦的检查过程 */ public interface BuyInOneClick extends Default, Billable{}
-
对于给定的接口Z,标记为属于组Z的约束(即,注解元素组包含接口Z的约束)或Z的任何父级接口(继承的组)都被视为组Z的一部分。
-
例子3.4、使用继承组
-
public class User{ @NotNull private String firstname; @NotNull(groups=Default.class) private String lastname; @NotNull(groups=Billable.class) private CreditCard defaultCreditCard; }
-
如果验证组BuyInOneClick将导致如下约束检查:
- @NotNull 在 firstname和lastname
- @NotNull 在defaultCreditCard
-
应该Default和Billable都是BuyInOneClick 父类接口
-
3.4.2、组的顺序
-
默认情况下,不按任何特定顺序验证约束,无论它们属于哪个组,但是,在某些情况下控制约束验证的顺序很有用。在很多情况下,应该先验证一组初步的限制条件,然后再严重其他限制条件。这是两个示例:
- 第二组依赖于稳定状态才能正常进行。第一组验证了此稳定状态。
- 第二组可能消耗CPU或内存开销比较大,应尽可能避免对其进行验证(提前筛选)
-
为了实现这种排序,可以将一个组定义为其他组的序列。当请求定义为序列的组时,必须按@GroupSequence.value定义的顺序依次处理组序列中的每个组。请注意,序列的一个组成员本身可以通过继承或序列定义由几个组组成。在这种情况下,每个组成的组必须遵循序列顺序。
-
第3.5节中定义了处理组,如果序列中处理的组之一生成一个或多个约束违例,则不得处理序列中的后续组。这样可以确保仅在另一组约束有效情况下才验证下一组约束。
-
定义序列的组和组成序列的组不得涉及到循环依赖性:
- 间接或直接
- 通过级联序列定义或组继承
-
如果一个组包含发现一个循环依赖问题,那么抛出GroupDefinitionException异常
-
定义序列的组不应该直接在约束声明中使用。换句话说,承载组序列的接口不应该在约束声明中使用。
-
要将组定义为序列,必须使用@GroupSequence注解对接口进行标记。
-
/** * 定义一个组序列 * 托管@GroupSequence的接口表示组序列。当托管一个类上时,代表该类的Default组。 * * @author Emmanuel Bernard * @author Hardy Ferentschik */ @Target({TYPE}) @Retention(RUNTIME) public @interface GroupSequence{ Class<?>[] value(); }
-
-
例子3.5、使用组序列
-
@ZipCodeCoherenceChecker(groups = Address.HighLevelCoherence.class) public class Address{ @NotNull @Size(max=50) private String street1; @NotNull @ZipCode private String zipcode; @NotNull @Size(max =30) private String city; /** * 检查整个对象的一致性 * 首先需要基本检查以确保绿色 */ public interface HighLevelCoherence{} /** * 检查基本约束和高级约束。 * 如果基本约束失败,则不检查高级约束 */ @GroupSequence({Default.class, HighLevelCoherence.class}) public interface Complete{} }
-
-
在示例3.5中,当Address.Complete组得到验证时,属于Default组的所有约束都得到验证。如果其他任何一个失败,则验证将跳过HighLevelCoherence组。如果所有默认约束均通过,则验证HighLevelCoherence约束。
-
注意事项:
- 给定的约束可以属于按序列排序的两个或多个组,在这种情况下,将约束作为第一组的一部分进行评估,并在随后的组中将其忽略。有关更多信息,请参见第3.5节。
3.4.3、类重新定义默认组
-
在示例3.5中,验证Default组不会验证HighLevelCoherence约束。为确保完成验证,用户必须使用完整组,这就破坏了您可能期望的某些封装。您可以通过重新定义Default组对于给定类的含义来解决此问题。要为类重新定义默认值,请在类上放置@GroupSequence注解;此序列表示将Default替换为此类的组的序列
-
例子3.6、重新为Address定义默认组
-
@GroupSequence({Address.class, HighLevelCoherence.class}) @ZipCodeCoherenceChecker(groups = Address.HighLevelCoherence.class) public class Address{ @NotNull @Size(max=50) private String street1; @NotNull @ZipCode private String zipcode; @NotNull @Size(max =30) private String city; /** * 检查整个对象的一致性 * 首先需要基本检查以确保绿色 */ public interface HighLevelCoherence{} }
-
-
在示例3.6中,当组Default验证地址对象时,将评估属于Default组并Address上所有的约束。如果验证成功了,则会评估地址中存在的所有HighLevelCoherence约束,换句话说,在验证地址的默认组时,将使用在地址类上定义的组顺序。
-
由于序列不能具有循环依赖性,因此在序列的表明中使用“默认”不是一种选择,托管在类A上并属于Defaut组(默认或显式)的约束隐式属于组A。
-
在类A上定义的序列(即重新定义该类的默认组)必须包含组A。换句话说。托管在类上的默认约束必须是序列定义的一部分。如果为类A重新定义Default组的@GroupSequence不包含组A,则在验证类或请求其元数据时会引发GroupDefinitionException。
3.4.4、隐式组
-
可以在同一组中隐式地对几个约束进行分组,而不必在约束声明中显示列出此类组。接口Z上托管的每个约束和Default组的一部分(隐式或显式)都属于Z组,这对基于接口表示的角色来验证对象的部分状态很有用。
-
例子3.7、接口和组托管约束
-
/** * 可审核对象合同 */ public interface Auditable{ @NotNull String getCreationDate(); @NotNull String getLastUpdate(); @NotNull String getLastModifier(); @NotNull String getLastReader(); } public class Order implements Auditable{ private String creationDate; private String lastUpdate; private String lastModifier; private String lastReader; private String orderNumber; public String getCreationDate(){ return this.creationDate; } public String getLastUpdate(){ return this.lastUpdate; } public String getLastModifier(){ return this.lastModifier; } public String getLastReader(){ return this.lastReader; } @NotNull @Size(min=10, max=10) public String getOrderNumber(){ return this.orderNumber; } }
-
在Defaul组上验证Order对象时,将验证以下约束:@NotNull (getCreateDate,getLastUpdate, getLastModifier, getLastReader,getOrderNumber); @Size(getOrderNumber) 它们都属于默认组。
-
在Auditable组上验证Order对象时,将验证以下约束: getCreateDate, getLastUpdate, getLastModifier,getLastReader上的@NotNull。当请求组Auditable时,仅验证存在于Auditable(及其任何父类接口)上且属于Default组的默认组的约束,它使得调用者可以验证给定的对象可以安全地审计,即使对象状态本身无效。
-
3.4.5、正式组的定义
-
定义组的正式规则如下。斜体文本是有关规则的注释。
-
对于每个类x:
-
A. 对于X的每个超类Y,组Y包含Y的组Y的所有约束
- 该规则为递归发现奠定正式的概念
-
B. 组x包含以下约束:
- 组x是在序列上使用的组,重新定义了类的默认组(请参阅第3.4.3节)
- 类X声明的每个约束都没有声明组,也没有明确声明组默认。
- x类上所有约束
- x所实现的任何接口声明的所有约束,且未注释@GroupSequence,其中没有显示声明或显式声明组Default
- 约束的接口上托管x的所有默认约束均由类层次结构的继承。接口标记@GroupSequence将被忽略。
- 如果X具有直接超类Y,则组Y中的每个约束。
- x的超类上托管的所有默认约束:约束由类层次结构继承。
- 类X声明的每个约束都没有声明组,也没有明确声明组默认。
- 组x是在序列上使用的组,重新定义了类的默认组(请参阅第3.4.3节)
-
C. 如果x没有@GroupSequence注解,则组default包含以下约束:
-
此规则定义在x上验证default时评估哪些约束
-
x的每一个约束
-
如果x具有直接超类Y,则默认y组中的每个约束
- 如果y重新定义默认组,则此规则是必须的
-
-
D. 如果X确实具有@GroupSequence注解,则组Default包含属于每个约束的每个约束@GroupSequence注解的声明的组。
- 该规则描述了一个类如何为其自身重新定义组Default(请参见第3.4.3节)
- @GroupSequence注解必须声明组X。
- 该规则描述了一个类如何为其自身重新定义组Default(请参见第3.4.3节)
-
E. 对于每个接口z,组z包含接下来的约束:
- 该规则定义了如何定义非默认组
- 接口z声明的每个约束,该约束未显式声明组或显式声明组Default
- z上托管的所有默认约束:此规则正式定义每个接口的隐式分组(请参阅第3.4.4节)
- 由未注解接口z的@GroupSequence的任何超级接口声明的每个约束(未明确声明组)
- 组z的接口上托管的所有默认约束都可以继承(请参阅第3.4.1节)
- 类x声明的每个约束,显式声明组z
- 由x托管并标记为属于组z的每个约束
- x所实现的任何接口声明的所有约束,且未注解@GroupSequence,其中明确声明组Z
- 由x的任何接口托管并标记为属于组z的每个约束
- 如果x具有直接超类y,则每个约束在y类的组z
- 由x的任何超类托管并标记为属于z组的每个约束
-
F . 对于每个标有@GroupSequence的接口z,组z包含属于每个约束的每个约束。@GroupSequence注解声明的组。
- 定义组序列的组成侧。但不定义序列的排序行为(请参阅第3.4.2节)
-
当请求给定组G(由接口G表示),已验证类x时:
- 评估属于组G的约束
- 如果接口G没有注解@GroupSequence,则由G的超级接口表示的每个组都将重新要求验证
- 如果接口G用了@GroupSequence注解,则由接口声明接口的表示的每个组需要@GroupSequence注解进行验证。
- 声明给@GroupSequence的组的验证必须按照@GroupSequence声明的顺序进行,将排序顺序传播到组成已排序组的组(通过继承或组序列)
- 如果组验证触发一个或多个约束的失败,则不得继续评估顺序中的组
- 如果组G代表被@GroupSequence覆盖的x的默认组,则等效于操作当通过@GroupSequence覆盖给定x的default组时,其验证如下:
- 请求@GroupSequence注解声明的接口表示的每个组进行验证。
- 对@GroupSequence声明的组的验证必须按照@GroupSequence声明的排序顺序进行:将排序顺序传播到组成已排序的组(通过继承或组序列)
- 如果组验证触发一个或多个约束的失败,则不得继续评估顺序中的组
-
除非由@GroupSequence定义,否则评估顺序不受约束,特别是,可以通过同一边验证几个组,如果组定义导致组之间的循环排序顺序,则会引发GroupDefinitionException
-
注意事项
- 被顺序(直接或间接)执行的G组在其自身之前执行不被视为循环引用。
-
3.5、验证路由
- 对于给定的组进行验证,应用于给定bean实例的验证例程应以不特定的顺序执行以下约束验证:
- 对于所有可达的字段,除非在此验证程序期间已经处理了给定导航路径的验证程序(参见第3.5.1节),否则执行匹配目标组的所有字段级验证(包括超类上表示)匹配的所有字段级验证(包括在超类上)
- 对于所有可达的getters,除非已经处理了给定导航路径的验证例程,否则执行匹配组的所有getter级别验证(包括在接口和次数上)匹配目标组(参见第3.5.1节)作为上一个组匹配的一部分。
- 执行匹配目标组的所有类级验证(包括在接口和超类),除非在此验证程序期间已经处理了给定导航路径的验证程序(参见第3.5.1节)作为上一个组的一部分匹配。
- 对于所有可访问和可级联的关联,执行所有级联验证(参见第3.5.1节),包括在接口和超类上表达的级联验证(参见第3.4.5节)
- 可达的字段,getters方法和关联都作为级联被定义在3.5.2节)
- 注意这就意味给定验证约束不会处理超过一次
- 除非是组序列是顺序的,组可以验证没有特定顺序,这个实现验证路由可以为多个组运行相同例程。
- 对象验证路由被描述如下,对于每个约束说明
- 确定约束声明,使用适当的ContraintValidator(参见 第3.5.3)
- 在适当的数据上执行约束验证实现的isValid方法
- 如果isValid返回true,继续执行下一个约束
- 如果isValid返回false,Bean验证提供统计ConstraintViolation对象去记录不符合验证规则违例(详情参见2.4节)
3.5.1、对象图验证
-
在给定关联的@Valid注解(即对象引用或集合,数组,对象的迭代器),决定Bean Validator实现以递归地应用Bean验证例程(每个)关联对象的验证例程。此机制是递归:关联对象本身可以包含级联引用。
-
null引用将会被忽略
-
为防止无限循环,如果在当前导航路径中已验证关联对象实例(从根对象开始),则Bean验证实现必须忽略级联操作。有关一个例子。请参阅3.8, 导航路径被定义为从根对象实例开始的一组@Valid关联,并到达关联的实例。给定的导航路径多次不能包含相同的实例(但是,虽然完整的验证的对象图)。有关示例,请参见示例3.8.
-
注意事项;
- 该对象图导航可以导致相同约束的多个验证和相同的对象实例,但是该组约束验证是确定性的,并且算法防止无限循环。
-
例子3.8、对象图限制
-
# 假设对象验证图顺序如下 Order -(lines)-> Orderline1 Order -(lines)-> Orderline2 Orderline1 -(order)-> Order Orderline2 -(order)-> Order Order -(customer)-> User Order -(shippingAddress)-> Address1 Order -(billingAddress)-> Address2 Address1 -(inhabitant)-> User Address2 -(inhabitant)-> User User -(addresses)-> Address1 User -(addresses)-> Address2 # 验证分支过程如下 Order -(lines)-> Orderline1 - order is ignored: Order is already present in the branch Order -(lines)-> Orderline2 - order is ignored: Order is already present in the branch Order -(customer)-> User -(addresses)-> Address1 - inhabitant is ignored: User is already present in the branch Order -(customer)-> User -(addresses)-> Address2 - inhabitant is ignored: User is already present in the branch Order -(shippingAddress)-> Address1 -(inhabitant)-> User - addresses to Address1 is ignored: Address1 is already present in the branch Order -(shippingAddress)-> Address1 -(inhabitant)-> User -(addresses)-> Address2 - inhabitant is ignored: User is already present in the branch Order -(billingAddress)-> Address2 -(inhabitant)-> User - addresses to Address2 is ignored: Address2 is already present in the branch Order -(billingAddress)-> Address2 -(inhabitant)-> User -(addresses)-> Address1 - inhabitant is ignored: User is already present in the branch
-
当找到相关对象上的失败约束时,构建ConstraintViolation对象,它们反映从根验证对象到达对象的路径(参见第4.2节)。
-
@Valid是一个正交的概念到组的概念,如果两个组依次执行,则第一组必须在评估第二组之前传递所有关联对象。但请注意,默认的组序列覆盖是本地定义类,并且它不会传播到关联对象,以下示例说明了:
-
例子3.9 重新定义Driver类的默认组
-
@GroupSequence({Minimal.class, Driver.class}) public class Driver{ @Min(value=18, groups=Minimal.class) int age; @Valid Car car; // setter/getters }
-
-
例子3.10 重新定义Car类的默认组
-
@GroupSequence({Car.class, Later.class}) public class Car{ @NotNull String type; @AssertTrue (groups = Later.class) Boolean roadWorthy; //setter/getters }
-
-
例子3.11定义组序列
-
@GroupSequence({Minimal.class, Later.class}) public interface SequencedGroups{}
-
-
例子3.12 组序列重写不会传播到关联对象
-
Validator validator = Validation.buildDefaultValidatorFactory().getVaildator(); Driver driver = new Driver(); driver.setAge(16); Car porche = new Car(); driver.setCar(porsche); Set<ConstraintViolation<Driver>> violations = validator.validate(driver); assert violations.size() == 2; violations = validator.validate(driver, SequencedGroups.class); assert violations.size() ==1;
-
默认组序列为driver以及car重新定义。默认组通过validator.validate(driver)请求默认组(driver),则在类driver使用Minimal组进行验证。由于示例中的驾驶员年龄仅为16,因此该约束将失败。由于重新定义Driver序列,所以passedDrivingTest将不会评估,但是,还有约束违例,即@Notnull上的Car.type,原因是,组默认值会传播到Car(Minimal不会), 类Driver定义其自己的组序列,这意味着只会评估仅@Notnull标注type字段。
-
在第二次调用验证,请求SequenceGroups组,其定义了Minimal、Later序列,在这种情况下,只有一次约束违规。再次@Min验证失败,但是这种情况下,Minimal组将传播到没有针对该组定义的任何约束的Car,属于该组的约束Later将不会被验证,直到属于Minimal通过的所有约束。
-
3.5.2、遍历属性
-
在某些情况下,不应访问某些属性的状态。例如,如果Java持久化提供程序加载的属性是延迟属性或延迟关联,则访问其状态将触发来自数据库的负载。不期望的行为。
-
Bean 验证提供一种控制哪种属性可以且无法通过TraversableResolver.isReachable()访问。
-
同样,尽管使用@Valid,但是仍然是级联验证的不希望的,Java持久性 例子2在flush操作不会级联关联到实体。您可以通过实现Traversable.isCascadable()方法。
-
/** * * 确定Bean验证提供程序是否可以访问属性,调用该类的方法,用于验证或级联的每个属性,可遍历的解析器实现必须是线程安全。 * * @author Emmanuel Bernard */ public interface TraversableResolver{ /** *确定允许Bean验证提供程序是否达到属性状态 *@param traversableObject 对象有traversableProperty,或如果validateValue被调用,这时为null *@param rootBeanType root的类型通过到Validator(验证) *@param pathToTraversableObject 路径是从root对象到traversableObject, *@param elementType FIELD或METHOD *@return 如果Bean Validation提供允许去获取这个属性状态,返回true,否则返回false */ boolean isReachable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elmentType); /** * 在bean实例上标记为@Valid是否允许级联验证 * 注意只有isReachable调用返回true时,如果属性标记为@Valid,那么与isReachable是相同的参数, *@param traversableObject 对象有traversableProperty,或如果validateValue被调用,这时为null *@param rootBeanType root的类型通过到Validator(验证) *@param pathToTraversableObject 路径是从root对象到traversableObject, *@param elementType FIELD或METHOD *@return 如果Bean Validation提供允许去级联验证,返回true,否则返回false */ boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType); }
-
调用isReachable可用于验证或级联访问的每个属性,如果此方法返回true,则可以访问属性。
-
isCascadable被调用所有关于级联的属性(即标记为@Valid),如果可访问它,则属性是cascadable ,如果isCascadable方法返回true。
-
注意事项:
- 只有isReachable调用返回true,才能调用isCascadable方法,换句话说,在调用isCasacadable之前调用isReacheable方法
-
traversableObject 就是代表对象实例将会被评估,如果检查是在validateValue调用中触发的,则返回null.
-
traversableProperty 是表示要遍历的traversableObject托管的属性的Node。属性的名称在3.1.2节中定义
-
rootBeanType 是正在验证(并传递到validate方法)的根的类。
-
pathToTraversableObject 表示从rootBeanType到traversableObject之间的Path(路径),如果根对象世俗traversableObject, pathToTraversableObject组成就是单个节点,同时名称为null,按照第4.2节(getPropertyPath)中描述的约定描述路径。
-
elementType 是放置注解的java.lang.annotation.ElementType。它可以是FIELD或METHOD,不应期望任何其他值。
-
Bean验证提供程序不得访问属性的状态,也不得在属性不可遍历时验证其约束,如果TraversableResolver为此属性返回true,则该属性是可遍历的。
-
当面TraversableResolver调用时发现异常,那么这个异常被包装成ValidationException
-
下面的示例假定示例3.13中定义的对象图,并假定将验证操作应用于地址对象。
-
例子3.13 定义一个Country
-
public class Country { @NotNull private String name; @Size(max = 2) private String ISO2Code; @Size(max = 3) private String ISO3Code; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getISO2Code() { return ISO2Code; } public void setISO2Code(String ISO2Code) { this.ISO2Code = ISO2Code; } public String getISO3Code() { return ISO3Code; } public void setISO3Code(String ISO3Code) { this.ISO3Code = ISO3Code; } }
-
public class Address{ @NotNull @Size(max=30) private String addressline1; @Size(max=30) private String addressline2; @Size(max=11) private String zipCode; @Valid private Country country; private String city; public String getAddressline1() { return addressline1; } public void setAddressline1(String addressline1) { this.addressline1 = addressline1; } public String getAddressline2() { return addressline2; } public void setAddressline2(String addressline2) { this.addressline2 = addressline2; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } @Size(max=30) @NotNull public String getCity(){ return city; } public void setCity(String city){ this.city; } public Country getCountry(){ return country; } public void setCountry(Country country){ this.country = country; } }
-
-
当Bean 验证提供者将要验证ISO3Code字段的约束,通过下列参数值它调用TraversableResolver.isReachable()的实例确保ISO3Code字段属性是可达的:
- traversableObject : country . 这个实例将会返回address.getCounry()
- traversableProperty: 一个节点名称为ISO3Code, traversableObject对应这个属性名称将会被验证。
- rootBeanType : Address.class 被验证的根对象
- pathtoTraversableObject: 这个路径包含单个名称为“country"的节点,这个路径是从address到country实例
- elementType: ElementType.FIELD. ISO3Code属性将被注解这个字段
-
当Bean验证提供者提供将要级联验证country(Address 对象),它将会调用TraversableResolver.isReachable()返回true,实例确保country属性是可达的。它采用如下参数调用TraversableResolver.isCascadable()方法:
- traversableObject: address. address实例
- traversableProperty: 节点名称为country, traverableObject对应这个名称的属性将会被验证
- rootBeanType: Address.class 被验证的根对象
- pathtoTraversableObject: 这个路径包含单个节点,节点名称为null
- elementType:ElementType.FIELD 这个country属性将会被注解到。
-
例子 3.14、 java 持久化关心TraversableResolver
-
public class JPATraversableResolver implements TraversableResolver{ public boolean isReachable(Object traversableObject, Path.Node traversableProprty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType){ return traversableObject == null || Persistence.getPersistenceUtil().isLoaded(traversableObject, traversableProperty.getName()); } public boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType){ return true; } }
-
Bean验证中默认使用的可遍历解析器的行为如下:
- 如果Java持久性在运行时环境中可用,则如果Java持久性将该属性视为已加载,则认为该属性可访问,一个典型的实现将使用Persistence.getPersistenceUtil().isLoaded(Object,String) 来实现这种协定
- 如果JavaPersistence 在运行时环境中不可用,则认为所有属性都可以访问的。
- 所有属性都考虑级联
-
请参阅第4.4节以了解如何传递自定义TraversableResolver
-
3.5.3、ConstraintValidator 解析算法
-
约束与一个或多个ConstraintValidator实现相关联,每个ConstraintValidator<A,T>接受类型T,执行的ConstraintValidator取决于托管约束的类型。对于给定的约束评估,应考虑单个ConstraintValidator.
-
如果约束声明托管在类或接口上,则目标类型为类或接口。如果约束托管在类属性上,则属性的类型为目标类型,如果约束托管在getter上,则getter的返回类型为目标类型。换句话说。解析算法认为类型是方法签名中定义的类型,而不是值的运行时类型。
-
下面编写的规则正式描述了以下语句:选择来验证声明的类型T的ConstraintValidator是这样的一种。其中ConstraintValidator支持的类型是T的超类型,并且没有其他ConstraintValidator支持的类型是T的超类型和不是所选ConstraintValidator支持类型的超类型。
-
当约束A置于目标声明的类型T,接下来解析规则应用如下:
-
基本数据类型等同于其包装类,因此基本数据的数组类型等同于包装类型的数组
-
如果T是U的子类型,则说ConstraintValidator<A,U>符合T(根据Java语言规范第3版第4.10章子类型[1])。请注意,如果T=U, 则T是U的子类型。
-
如果在约束A列出的ConstraintValidator中未找到符合T的ConstraintValidator,则引发UnexceptedTypeException。
-
如果U 是 V的子类,那么从考虑严格性一个ConstraintValidator<A,U>比一个ConstraintValidator<A,V>更具体,如果U是V的子类且U !=V,我们称为U是V严格子类(根据Java语言规范第3版第4.10章子类型[2])
-
如果没有其他符合T的ConstraintValidator<A,V>严格比ConstraintValidator<A,U>更具体,则认为符合T的ConstraintValidator<A,U>是最具体的。
-
如果有多个最具体的ConstraintValidator,那么UnexceptedTypeException将会抛出
-
注意事项:
- 虽然Java编译器本身无法确定约束声明是否会导致UnexpectedConstraint声明和验证过程应用这些规则,并避免在模棱两可的情况下进行编译,该规范鼓励Bean验证提供者向其用户提供这种工具。
-
让我们来看几个声明及其各自的ConstraintValidator分别率。假设如3.15所示:
-
例子3.15 ConstraintValidator 和类型解析
-
[...] @Constraint(validatedBy={ SizeValidatorForCollection.class, SizeValidatorForSet.class, SizeValidatorSerializable.class }) public @interface Size{...} public class SizeValidatorForCollection implements ConstraintValidator<Size, Collection>{...} public class SizeValidatorForSet implements ConstraintValidator<Size,Set>{...} public class SizeValidatorForSerializable implements ConstraintValidator<Size, Serializable>{...} public interface SerializableCollection extends Serializable, Collection {}
-
这个解析展示如下表格3.1
-
声明 解析 @Size Collection getAddresses(){…} 直接匹配 SizeValidatorForCollection @Size Collection<?> getAddresses(){…} SizeValidatorForCollecation : Collection是 Collection<?>的父类 @Size Collection getAddresses(){…} SizeValidatorForCollection: Collection是Collection的父类 @Size Set getAddresses(){…} SizeValidatorForSet: Set是Set父类 @Size SortedSet getAddresses(){…} SizeValidatorForSet: Set 是SortedSet的父类 @Size SerializableCollection getAddresses(){…} UnexpectedTypeException抛出:因为SerializableCollection 的父类既有Collection和Serializable,且Collection 和Serializable无父子关系。 @Size String getName(){…} UnexpectedTypeException抛出:因为没有ConstraintValidator类没有是String的父类处理器
-
-
3.6、例子
-
第一个示例演示了如何注解bean,字段和getter以表达一些约束
-
@ZipCodeCityCoherenceChecker public class Address{ @NotNull @Size(max=30) private String addressline1; @Size(max=30) private String addressline2; @Size(max=11) private String zipCode; private String city; public String getAddressline1() { return addressline1; } public void setAddressline1(String addressline1) { this.addressline1 = addressline1; } public String getAddressline2() { return addressline2; } public void setAddressline2(String addressline2) { this.addressline2 = addressline2; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } @Size(max=30) @NotNull public String getCity(){ return city; } public void setCity(String city){ this.city; } }
-
在对Address对象执行验证例程期间
- addressline1字段值传递给@NotNull 以及@Size约束验证实现。
- addressline2字段值传递给@Size约束验证实现
- getCity 值传递给@Size和@NotNull 约束验证实现
- @ZipCodeCoherenceChecker 的约束验证实现了传递Address对象参数的isValid方法
-
-
第二例子展示对象图验证 3.17
-
public class Country { @NotNull private String name; @Size(max = 2) private String ISO2Code; @Size(max = 3) private String ISO3Code; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getISO2Code() { return ISO2Code; } public void setISO2Code(String ISO2Code) { this.ISO2Code = ISO2Code; } public String getISO3Code() { return ISO3Code; } public void setISO3Code(String ISO3Code) { this.ISO3Code = ISO3Code; } } public class Address{ @NotNull @Size(max=30) private String addressline1; @Size(max=30) private String addressline2; @Size(max=11) private String zipCode; @NotNull @Valid private Country country; private String city; public String getAddressline1() { return addressline1; } public void setAddressline1(String addressline1) { this.addressline1 = addressline1; } public String getAddressline2() { return addressline2; } public void setAddressline2(String addressline2) { this.addressline2 = addressline2; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } @Size(max=30) @NotNull public String getCity(){ return city; } public void setCity(String city){ this.city; } public Country getCountry(){ return country; } public void setCountry(Country country){ this.country = country; } }
-
在对Address对象执行验证例程期间,将处理对addressLine1, addressLine2, zipCode, getCity和country的约束,以及对country对象本身验证,尤其是对country.name进行@NotNull, ISO2Code和ISO3Code检查@Size
-
假设@NotEmty定义如下:
-
@Documented @NotNull @Size(min=1) @ReportAsSingleViolation @Constraint(validatedBy =NotEmpty.NotEmptyValidator.class) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface NotEmpty{ String message() default "{com.acme.constraint.NotEmpty.message}"; Class<?> groups() default {}; Class<? extends Payload>[] payload() default{}; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @interface List{ NotEmpty[] value(); } class NotEmptyValidator implements ConstraintValidator<NotEmpty, String> { public void initialize(NotEmpty constraintAnnotation){} public boolean isValid(String value, ConstraintValidatorContext context){ return true; } } }
-
-
第三个例子 展示超类、继承和组合约束
-
public interface Person{ @NotEmpty String getFirstName(); String getMiddleName(); @NotEmpty String getLastName(); } public class Customer implements Person{ private String firstName; private String middleName; private String lastName; @NotNull private String customerId; @Password(robustness=5) private String password; public String getFirstName(){ return firstName; } public void setFirstName(String firstName){ this.firstName = firstName; } public String getMiddleName(){ return middleName; } public String setMiddleName(String middleName){ this.middleName = middleName; } public String getLastName(){ return lastName; } public void setLastName(String lastName){ this.lastName = lastName; } public String getCustomerId(){ return customerId; } public void setCustomerId(String customerId){ this.customerId = customerId; } public String getPassword(){ return password; } public void setPassword(String password){ this.password = password; } } public class PreferredGuest extends Customer{ @CreditCard private String guestCreditCardNumber; public String getGuestCreditCardNumber(){ return guestCreditCardNumber; } public void setGuestCreditCardNumber(String guestCreditCardNumber){ this.guestCreditCardNumber = guestCreditCardNumber; } } public class CommonGuest extends customer{}
-
当验证PreferredGuest,如下约束将会被验证
- @NotEmpty, @NotNull 和@Size(min=1)标记到firstName
- @NotEmpty, @NotNull和@Size(min=1)标记到lastName
- @NotNull 标记在customerId, @Password 标记在password
- @CreditCard 标记在guestCreditCardNumber
-
当验证CommonGuest, 如下约束将会被验证
- @NotEmpty, @NotNull 和@Size(min=1)标记到firstName
- @NotEmpty, @NotNull和@Size(min=1)标记到lastName
- @NotNull 标记在customerId, @Password 标记在password
-
-
第四个例子,3.19,展示组序列的影响
-
@GroupSequence({First.class, Second.class, Last.class}) public interface Complete{} public class Book{ @NotEmpty(groups=First.class) private String title; @Size(max=30, groups=Second.class) private String subtitle; @Valid @NotNull(groups=First.class) private Author author; public String getTitle(){ return title; } public void setTitle(String title){ this.title=title; } public String getSubtitle(){ return subtitle; } public void setSubtitle(String subtitle){ this.subtitle = subtitle; } public Author getAuthor(){ return author; } public void setAuthor(Author author){ this.author = author; } } public class Author{ @NotEmpty(groups=Last.class) private String firstName; @NotEmpty(groups=First.class) private String lastName; @Size(max=30, groups=Last.class) private String company; public String getFirstName(){ return firstName; } public void setFirstName(String firstName){ this.firstName = firstName; } public String getLastName(){ return lastName; } public void setLastName(String lastName){ this.lastName = lastName; } public String getCompany(){ return company; } public void setCompany(String company){ this.company = company; } }
-
假设验证Complete组使用如下book实例
-
Author author = new Author(); author.setLastName("Baudelaire"); author.setFirstName(""); Book book = new Book(); book.setAuthor(author);
-
这个验证路由将会返回失败:
-
@NotNull 失败(来自于@NotEmpty)标记在title字段上
-
作为title和author.lastname都被检查为First 组,如果实例更新为
-
book.setTitle("les fleurs du mal"); author.setCompany("Some random publisher with a very very very long name");
-
-
这个验证路由将会返回失败:
- author.firstName 失败 通过 @Size(min=1)(来自@NotEmpty)约束
- author.company 失败 通过@Size约束
-
也就是说验证First和Second 组时没有任何错误,Last 组将会验证失败
-