约束是通过约束注解和约束验证实现列表的组合来定义的。在组合的情况下,约束注解将应用于类型,方法,字段或其他约束注解。
除非另有说明,否则Bean验证API的默认包名称为javax.validation.
2.1、约束注解
-
JavaBean上的约束通过一个或多个注解来表示,如果注解的保留策略包含RUNTIME,并且注解本身使用javax.validation.Constraint注解,则该注解被视为约束定义。
-
/** * 约束注解以及约束验证实现之间的链接 * 给定的约束注解应使用@Constraint注解进行标记,该注解引用其约束验证实现的列表。 * @author Emmanuel Bernard * @author Gavin King * @author Hardy Ferentschik */ @Documented @Target({ANNOTATION_TYPE}) @Retention(RUNTIME) public @interface Constraint{ /** * CosntraintValidator类必须引用不同的目标类型,如果两个ConstraintValidator引用相同的类型,则会发生异常。 * @return 实现约束的ConstraintValidator类的数组 */ public class<? extends ConstraintValidator<?,?>>[] validatedBy(); }
-
约束注解可以针对以下任何元素类型(ElementTypeS):
- FIELD 字段约束属性
- METHOD 约束getters方法
- TYPE 约束 bean
- ANNOTATION_TYPE 用于构建其他约束的约束
-
虽然不禁止其他ElementType, 但提供程序不必识别和处理对此类类型的约束。内置类型确实支持PARAMETER和CONSTRUCTOR以允许Bean验证提供程序特定的扩展,对自定义注解采用相同的方式被认为最佳实践。
-
由于给定的约束定义适用于一种或多种特定的Java类型,因此用于约束注解的JavaDoc应该清楚说明支持哪些类型。将约束注解应用于不兼容的类型将引发UnexpectedTypeException。在定义ConstraintValidators列表时应格外小心。如果ConstraintValidator列表导致模棱两可,则类型解析算法(请参阅第3.5.3节)可能导致异常。
-
如果约束定义无效,则在验证时或请求元数据时引发ConstraintDefinitionException.无效的约束定义的原因是多种多样的,但包括缺少或非法的消息(message)或组(grooups)元素(请参阅第2.1.1节)
2.1.1、约束定义属性
- 约束定义可以具有在将约束应用于JavaBean时指定的属性,这些属性被映射为注解元素,注解元素名称:message、groups和payload被视为保留名称,不允许以valid开头的注解元素,约束可以使用其他元素名称作为其属性。
2.1.1.1、message
-
每个约束注解都必须定义String类型的message元素
-
String message() default "{com.acme.constraint.MyConstraint.message}";
-
message 元素值用于创建错误的消息。有关详细说明,请参见第4.3节,建议将默认消息值设置为资源束的key以启用国际化。还建议使用以下约定:资源束key应该是连接到.message的约束注解的完全限定的类名,如上程序片段所示
-
内置Bean验证约束遵循此约定
2.1.1.2、groups
-
每个约束注解都必须定义一个groups元素,该元素指定与约束声明关联的处理组。
-
Class<?>[] groups() default {};
-
默认值必须空数组
-
如果在声明元素约束时未指定任何组,则将默认组视为已声明
-
有关更多信息,请参见第4.1.2节
-
组通常用于控制评估约束的顺序,或用于验证JavaBean的部分状态。
2.1.1.3、payload
-
约束注解必须定义一个playload元素,该元素指定与约束声明相关联的有效负载。
-
Class<? extends Payload>[] payload() default {};
-
默认值必须空数组
-
每个可附加的有效负载实现类需要扩展了PayLoad接口
-
/** * 可以附加到给定约束声明的payloads类型。 * payloads 通常用于承载验证客户端的元素信息。 * payloads使用不被认为是可移植的。 * @author Emmanuel Bernard * @author Gerhard Petracek */ public interface Payload{ }
-
验证客户端通常使用payloads将某些元数据信息与给定的约束声明相关联。payloads通常是不可移植的。与基于字符串的方法相反,将payloads描述为接口扩展允许一种更简单,更类型安全的方法。
-
示例2.1中所示:payload的一个用例是将严重性与约束相关联,表示框架可以利用这种严重性来调整约束失败的显示方式。
-
例子2.1 使用payload将严重性与约束相关联。
-
package com.acme.severity public class Severity{ public static class info implements Payload {}; pulbic static class Error implements Payload{}; } public class Address{ @NotNull(message="would be nice if we had one", payload=Severity.Info.class) public String getZipCode(){...} @NotNull(message="the city is mandatory", payload=Severity.Error.class) String getCity(){...} }
-
可以通过ConstraintDescriptor从错误报告中检payload信息,该ConstraintDescriptor可以通过ConstraintViolation对象(请参见第4.2节)或通过元数据API(请参见第5.5节)进行访问。
2.1.1.4、约束具体的参数
- 约束注解定义可以定义附加元素以参数化约束,例如,验证字符串长度的约束可以使用名称为length的注解元素在声明该约束时指定最大长度。
2.1.2、例子
-
例子2.2、简单约束定义
-
package com.acme.constraint; @Documented @Constraint(validatedBy = OrderNumberValidator.class) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface OrderNumber{ String message() default "{com.acme.constraint.OrderNumber.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
-
例子2.2将String标记为格式正确的订单号,约束验证器由OrderNumberValidator实现。
-
例子2.3、带有默认参数的约束定义
-
package com.acme.constraint; /** * 人类听力声音范围(Hz) ,不同年龄人可以接收到不同数值 */ @Documented @Constraint(validatedBy = AudibleValidator.class) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface Audible{ Age age() default Age.YOUNG; String message() default "{com.acme.constraint.Audible.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default{}; public enum Age{ YOUNG, WONDERING, OLD } }
-
例2.3确保给定的频率在人耳的范围内,约束定义包括一个可选参数,可以在应用约束时指定该参数。
-
例子2.4、带强制性参数的约束定义
-
/** * 定义一个int类型数数组,可以接受int或Integer对象 */ @Documented @Constraint(validatedBy=DiscreteListOfIntegerValidator.class) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface Acceptable{ int[] value(); String message() default "{com.acme.constraint.Acceptable.message}"; Class<?> groups() default{}; Class<? extends Payload>[] payload() default{}; }
-
例2.4定义了一个表示数组的可接受的列表,在应用约束时必须指定value属性。
2.2、应用相同类型的多个约束
-
对具有不同属性的同一目标多次声明同一约束通常很有用。一个常见的示例是@Pattern约束,它可以验证其目标是否与指定的正则表达式匹配。其他约束也有此要求。相同的约束类型可以属于不同的组,并且根据目标组具有特定的错误消息。
-
为了支持此要求,bean验证提供程序将以特殊方式对待其值元素具有约束注解的数组的返回类型的常规注解(未由@Constraint注解的标记)。Bean Validation实现将值数组中的每个元素中指定的每个约束应用于目标,注解必须具有保留RUNTIME,并且可以应用于类型,字段,属性或其他注解,建议使用与初始约束相同的目标集。
-
约束设计者的注意事项:每个约束注解都应与其对应的多值注解一起使用。规范建议(尽量不是强制性的)建议定义名为List的内部注解。
-
例子2.5、多值约束定义
-
/** * 对于给定国家验证邮编, 只支持String类型 */ @Documented @Constraint(validatedBy = ZipCodeValidator.class) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface ZipCode{ String countryCode(); String message() default "{com.acme.constraint.ZipCode.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; /** * 在相同元素上定义多个@ZipCode注解 */ @Target({METHOD,FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @interface List{ ZipCode[] value(); } }
-
例子2.6、多值约束声明
-
public class Address{ @ZipCode.List({ @ZipCode(countryCode="fr", groups=Default.class message = "zip code is not valid"), @ZipCode(countryCode="fr", groups=SuperUser.class message="zip code invalid. Requires overriding before saving.") }) private String zipcode; }
-
在此示例中,这两个约束均适用于邮政编码字段,但是具有不同的组和不同的错误消息。
-
在同一个目标(即类或属性)上针对相同的基础约束类型使用两个不同的多约束注解不会被认为是可移植的,因此不建议使用。
2.3、约束构成
-
该规范允许您组合约束以创建更高级别的约束。约束组合在几种方面很有用:
- 避免重复并促进复用更原始的约束
- 在元数据API中公开原始约束作为组合约束的一部分,并增强工具意识。
-
通过使用组合的约束注解对约束注解进行标记来完成组合
-
例子2.7、通过注解组合约束来完成组合
-
@Pattern(regexp="[0-9]*") @Size(min= 5, max=5) @Constraint(validatedBy = FrenchZipcodeValidator.class) @Documented @Target({ANNOTATION_TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface FrenchZipcode{ String message() default "Wrong zipcode"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @interface List{ FrenchZipcode[] value(); } }
-
用@FrenchZipcode(组合的注解) 注解元素等效与使用@Pattern(regexp="[0-9]*"), @Size(min=5, max=5)(组成注解) 和@FrenchZipcode注解, 更正式地讲,将托管在约束注解上的每个约束注解应用于目标元素,然后以递归方法进行。注意,还应用了主注解及约束验证实现。默认情况下,每个失败的约束都会生成错误报告。主约束注解中的组由组合注解继承。组成注解上的粉盒组定义都将被忽略。同样,主约束注解中的payload也由组合注解继承,组成注解上的任何payload定义都将被忽略。
-
放置组会约束的属性类型必须与所有约束(组合中和已经组合)兼容,约束设计者应确保保存这种类型,并在JavaDoc中列出所有兼容的类型。
-
可以确保组合的注解不会引发单个错误报告。在这种情况下,如果一个多个组合注解无效,则自动将主要约束视为无效,并生成相应的错误报告。要在组合约束或组合约束之一失败时将约束标记为引发单个约束错误报告,请使用@ReportAsSingleViolation注解。
-
例子2.8、如果任何组合约束失败,则会引发与@FrenchZipcode对应的错误报告,而不会出现其他错误。
-
@Pattern(regexp="[0-9]*") @Size(min= 5, max=5) @Constraint(validatedBy = FrenchZipcodeValidator.class) //增加注解 @ReportAsSingleViolation @Documented @Target({ANNOTATION_TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface FrenchZipcode{ String message() default "Wrong zipcode"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @interface List{ FrenchZipcode[] value(); } }
-
@ReportAsSingleViolation定义类如下
-
/** * 如果任何组合的注解失败,则托管此注解的约束注解将返回组合的注解错误报告,每个单独的构成约束的错误报告都将被忽略 * @author Emmanuel Bernard */ @Target({ANNOTATION_TYPE}) @Retention(RUNTIME) public @interface ReportAsSingleViolation{ }
-
更具体地,如果组合约束被标记为@ReportAsSingleViolation,并如果其任何组合约束报告一个或多个违例,则来自组成单个约束的所有报告都会被忽略,并生成与组合约束相对应的错误报告。
-
注意事项:
- 如果一个组合约束失败,并且如果将组合约束标记为@ReportAsSingleViolation,则Bean验证提供程序可以自由地不处理该组合约束的其他组合约束。
-
组合注解可以定义message和自定义属性(不包括groups和payload),但是这些在固定的约束定义中是固定的。
-
示例2.9注解可以使用属性,它们针对给定的主要注解是固定的,所有@FrenchZipcode约束都将@Size限制为5.
-
@Pattern(regexp="[0-9]*") @Size(min= 5, max=5) @Constraint(validatedBy = FrenchZipcodeValidator.class) @Documented @Target({ANNOTATION_TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface FrenchZipcode{ String message() default "Wrong zipcode"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @interface List{ FrenchZipcode[] value(); } }
-
可以覆盖在组合注解上定义的属性和消息,来自主注解的属性用于覆盖组合注解的一个或多个属性,此类属性使用@OverridesAttribute注解或其等效的多值@OverridesAttribute.List进行注解。
-
例子2.10,组合注解的属性可以主组合注解的属性覆盖。
-
@Pattern(regexp="[0-9]*") @Size(min= 5, max=5) @Constraint(validatedBy = FrenchZipcodeValidator.class) @Documented @Target({ANNOTATION_TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface FrenchZipcode{ String message() default "Wrong zipcode"; Class<?>[] groups() default {}; Class<? extends Payload> [] payload() default {}; @OverridesAttribute.List({ @OverridesAttribute(constraint=Size.class, name="min"), @OverridesAttribute(constraint=Size.class, name="max") }) int size() default 5; @OverridesAttribute(constraint=Size.class, name="message") String sizeMessage() default "{com.acme.constraint.FrenchZipcode.zipcode.size}"; @OverridesAttribute(constraint=Pattern.class, name="message") String numberMessage() default "{com.acme.constraint.FrenchZipcode.number.size}"; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @interface List{ FrenchZipcode[] value(); } }
-
用@OverridesAttribute(@FrenchZipcode.sizeMessage)注解组合约束属性的值将应用于以OverridesAttribute.name命名的组合约束属性,并托管在OverridesAttribute.constraint(@Size.message)类型的组合约束上,同样,@FrenchZipcode.numberMessage值被映射到@Pattern.mesage。如果未定义,则@OverridesAttribute.name的默认值是托管@OverridesAttribute注解的组合约束属性的名称。
-
被覆盖和覆盖属性的类型必须相同。
-
注意事项
- 组成约束本身可以组合约束,在这种情况下,将根据所述规则递归覆盖属性值。但是请注意,转发规则(由@OverridesAttribute定义)仅应用于直接组成约束。
-
使用例子2.10
-
@FrenchZipcode(size=9, sizeMessage="Zipcode should be of size {max}")
-
等同于@FrenchZipcode, (如果@FrenchZipcode定义如下)
-
@Pattern(regexp="[0-9]*") @Size(min= 9, max=9, message="Zipcode should be of size {max}") @Constraint(validatedBy = FrenchZipcodeValidator.class) @Documented @Target({ANNOTATION_TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface FrenchZipcode{ String message() default "Wrong zipcode"; Class<?>[] groups() default {}; Class<? extends Payload> [] payload() default {}; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @interface List{ FrenchZipcode[] value(); } }
-
-
如果一个约束被多次用作组成约束,则使用第2.2节中描述的多值约束模型。要选择特定的组成约束,请使用OverridesAttribute.constraintIndex。 它表示值数组中的约束索引。如果未定义index,则将针对单个约束声明。
-
例子2.11、在@OverridesAttribute使用constraintIndex
-
@Pattern.List({ @Pattern(regexp="[A-Z0-9._%+-]+@[A-Z0-9._%+-]+\.[A-Z]{2,4}"),//email @Pattern(regexp=".*?emmanuel.*?") // emmanuel }) @Constraint(validatedBy={}) @Documented @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) public @interface EmmanuelsEmail{ String message() default "Not emmanuel's email"; @OverridesAttribute(constraint=Pattern.class, name="message", constraintIndex=0) String emailMessage() default "Not an email"; //对应不同类型不同message信息 @OverridesAttribute(constraint=Pattern.class, name="message", constraintIndex=1) String emmanuelMessage() default "Not Emmanuel"; Class<?> [] groups() default {}; Class<? extends Payload>[] payload() default(); @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @interface List{ EmmanuelsEmail[] values(); } }
-
@OverridesAttribute定义如下:
-
/** * 将属性标记为覆盖组合约束的属性。这两个属性必须共享相同的类型 * @author Emmanuel Bernard */ @Target({METHOD}) @Retention(RUNTIME) public @interface OverridesAttribute{ /** * @return 返回约束类型属性被覆盖 */ Class<? extends Annotation> constraint(); /** * 约束属性的名称被覆盖。 * 默认为托管@OverridesAttribute的属性名称 * @return 约束名称已经被覆盖 */ String name(); /** * 使用多个相同类型的约束时,目标约束声明的索引。索引表示value()数组中约束的索引。默认情况下,未定义索引,并且针对单个约束声明 *@return 约束声明索引(如果使用了多值注解) * */ int constraintIndex() default -1; /** *定义多个OverridesAttribute */ @Documented @Target({METHOD}) @Retention(RUNTIME) public @interface List{ OverridesAttribute[] value(); } }
-
以下元素唯一标识覆盖的约束属性:
- OverridesAttribute.constraint
- OverridesAttribute.name
- OverridesAttribute.constraintIndex
-
如果组合是无效(无限递归组合,错误属性覆盖,单个属性映射到多个源属性等),则在验证时或请求元数据时引发ConstraintDefinitionException
-
鼓励约束设计人员根据规范定义的内置约束(无论是否递归)使用组合,组合约束通过Bean验证元数据API(第5.5节)公开,该元数据对于第三方元数据使用者特别有用,例如持久性框架生成数据库模式(例如Java持久化)或表示框架。
-
2.4、约束验证实现
-
约束验证实现对给定类型的给定约束注解执行验证。实现类由装饰约束定义的@Constraint注解的validatedBy元素指定。约束验证实现ConstraintValidator接口。
-
/** * 定义用于验证给定对象类型T的给定约束A的逻辑,实现必须遵守以下约束: * T 必须解析为非参数化类型 或 T的泛型参数必须是无边界通配符类型。 * * @author Emmanuel Bernard * @author Hardy Ferentschik */ public interface ConstraintValidator<A extends Annotation, T> { /** * 初始化验证程序以准备isValid调用。传递给定约束声明的约束注解 * 确保在将此实例用于验证之前先调用此方法。 *@param constraintAnnotation是给定约束声明的注解实例 * */ void initialize(A constraintAnnotation); /** * 实现验证逻辑。value的状态不得更改,此方法可以并发访问,实现必须确保线程安全 * @param value 需要验证对象 * @param context 验证时的上下文信息 * @return false 如果验证不通过时候 返回false */ boolean isValid(T value, ConstraintValidatorContext context); }
-
一些限制适用于泛型T(在isValid方法中使用),T必须满足以下要求
- 解析成非参数化类型(例如,因为该类型未使用泛型或因为使用原生类型而不是泛型版本)
- T的泛型参数必须是无边界的通配符类型(例如<?>)
-
例子 2.12. 有效 ConstraintValidator 定义
-
// String 类型并不是使用泛型 public class SizeValidatorForString implements ConstraintValidator<Size, String> {...} // Collection使用泛型,但是元素是确定原生类型 public class SizeValidatorForCollection implements ConstraintValidator<Size, Collection>{...} // Collection 使用泛型且是无界通配符类型 public class SizeValidatorForCollection implements ConstraintValidator<Size, Collection<?>> {...}
-
例子2.13、无效ConstraintValidator 定义
-
// 参数类型 public class SizeValidatorForString implements ConstraintValidator<Size, Collection<String>>{...} // 使用有边界通配符参数化类型 public class SizeValidatorForCollection implements ConstraintValidator<Size,Collection<? extends Address>>{...}
-
注意事项:
-
此限制不是理论上的限制,并且该规范将来版本可能会允许它。约束验证实现实例的生命周期未定义。允许兼容的实现缓存从ConstraintValidatorFactory检索的ConstraintValidator实例。
-
在使用约束实现之前,Bean 验证提供程序将调用initialize方法。每次验证给定值时,Bean验证提供程序都会对isValid方法进行调用,如果该值无效,则返回false,否则返回true,isValid 实现必须是线程安全的。
-
如果属性是一个非预期类型,则引发UnexpectedTypeException,如果ConstraintValidator实现收到不支持的类型,则他们自身会引发此异常,但是,鼓励约束设计器使用专门的ConstraintValidator实现,并将类型解析委托给Bean验证提供程序(请参阅第3.5.3节中描述的类型匹配算法)
-
如果在initialize方法或isValid方法中发生异常,则Bean验证引擎会将运行时异常包装到ValidationException中。约束验证实现不允许更改传递给isValid的值的状态。
-
-
注意事项
- 虽然不是强制性的,但将核心约束验证与非null约束验证分开是一种最佳实践(例如,@Email约束将在null对象上返回true,即不会处理@NotNull验证),null可以有多种含义,但通常用于表示一个值没有任何意义,不可用或只是未知,在大多数情况下,这些对值的约束与其他约束正交,例如,字符串(如果存在)必须是电子邮件,但可以为null,分开考虑这两个问题是一种最佳实践。
-
传递给isValid方法的ConstraintValidatorContext对象包含在验证约束的上下文中可用的信息和操作
-
/** *应用给定的约束验证器时提供上下文数据和操作 *至少必须定义一个ConstraintViolation(如果是默认的ConstraintViolation被禁用,则为默认一个,即为自定义的)。 *@author Emmanuel Bernard */ public interface ConstraintValidatorContext{ /** * * 禁用默认的ConstraintViolation对象生成(使用约束中声明的消息模板)。可用于设置其他违规消息或基于其他属性生成ConstraintViolation * */ void disableDefaultConstraintViolation(); /** * * * @return 当前未插入值的默认消息。 */ String getDefaultConstraintMessageTemplate(); /** * 返回一个约束违例生成器,构建一个违例报告,运行将其选择性第关联到子路径,将插入违例消息。 * 要创建ConstraintViolation必须调用fluent API的一个接口中可用的#addConstraintViolation()方法之一。如果在ConstraintViolationBuilder上的@addConstraintViolation()或其任何关联的嵌套接口之后调用了另一个方法,则会引发IllegalStateException 异常。 * 如果isValid返回false,则将根据每个ConstraintViolation报告构建一个ConstraintViolation对象,包括默认报告(除非已使用#disableDefaultConstraintViolation()被调用),除非已覆盖路径,否则通过这样的调用生成ConstraintViolation对象包含相同的上下文信息(root bean,path等) * 若要创建不同的ConstraintViolation,一个新的约束违例构建器必须从ConstraintValidatorContext中取出。 * 这里展示一下用法示例 * // 使用约束的默认路径创建一个新的violation 报告 * context.buildConstraintViolationWithTemplate("way too long") * .addConstraintViolation(); * // 在默认路径子节点"street"下创建新的违例报告 * context.buildConstraintViolationWithTemplate("way too long") * .addNode("street") * .addConstraintViolation(); * //在默认路径addresses["home"].city.name子节点创建新违例报告 * context.buildConstraintViolationWithTemplate("this detail is wrong") * .addNode("addresses") * .addNode("country").inIterable().atKey("home") * .addNode("name") * .addConstraintViolation(); * @param messageTemplate 新的未插入的约束消息 * @return 返回一个约束违例构建器 */ ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate); /** * ConstraintViolation构建器允许选择将违规报告关联到子路径。 * 要创建ConstraintViolation,必须调用fluent API的一个接口中可用的#addConstraintViolation()方法之一。如果在ConstraintViolationBuilder上的#addConstraintViolation()或其任何关联对象之后调用另一个方法,则会引发IllegalStateException * */ interface ConstraintViolationBuilder{ /** *将节点添加到将与ConstraintViolation关联的路径。 name描述单个属性,特别是,不允许使用点(.)。 * @param name 属性名称 * @return 创建一个代表节点名称的构建器 * */ NodeBuilderDefinedContext addNode(String name); /** * * 如果约束验证器将该值标记为无效,则添加要生成的新ConstraintViolationBuilder实例及其嵌套对象的方法从以下位置返回IllegalStateException * @return 从ConstraintViolationBuilder中获取实例ConstraintValidatorContext * */ ConstraintValidatorContext addConstraintViolation(); /** * 代表一个节点,且上下文是可知的(例如索引,键和isInIterable (内部迭代) * */ interface NodeBuilderDefinedContext{ /** * *将节点添加到将与ConstraintViolation关联的路径。 name描述单个属性,特别是,不允许使用点(.)。 * @param name 属性名称 * @return 创建一个代表节点上下文 * * */ NodeBuilderCustomizableContext addNode(String name); /** * * 如果约束验证器将该值标记为无效,则添加要生成的新的ConstraintViolation.此对象来自ConstraintViolationBuilder实例的方法,并且约束违例生成器嵌套对象在此调用后返回IllegalStateException * *@return ConstraintViolationBuilder中取出ConstraintValidatorContext实例 */ ConstraintValidatorContext addConstraintViolation(); } /** * *代表一个节点,且上下文是可知的(例如索引,键和isInIterable (内部迭代) * * */ interface NodeBuilderCustomizableContext{ /** * * * 将节点标记为位于Iterable或Map中 * @return 表示可迭代细节的构建器 */ NodeContextBuilder inIterable(); /** * * 将节点添加到将与ConstraintViolation关联的路径name描述单个属性,特别是不允许点(.) *@param name 属性名称 *@return 构建这个节点的上下文 */ NodeBuilderCustomizableContext addNode(String name); /** * *如果约束验证器将该值标记为无效,则添加要生成的新的ConstraintViolation.此对象来自ConstraintViolationBuilder实例的方法,并且约束违例生成器嵌套对象在此调用后返回IllegalStateException * @return ConstraintViolationBuilder 取出ConstraintValidatorContext实例 * */ ConstraintValidatorContext addConstraintViolation(); } /** *表示在iterator 或Map中的节点的优化选择。如果迭代器是索引集合或映射,则 *应设置索引或键 * */ interface NodeContextBuilder { /** * * * 定义对象进入Map的键 *@param key map的键 * @return 代表当前节点的构建器 */ NodeBuilderDefinedContext atKey(Object key); /** * * 定义对象到List货数组的索引 *@param index 索引 *@return 代表当前节点的构建器 * */ NodeBuilderDefinedContext atIndex(Integer index); /** *将节点添加到与ConstraintViolation关联的路径 *name的描述是一个单一属性,特别说明,不允许出现点(.) * @param name 属性名称 * @return 代表当前节点的构建器 */ NodeBuilderCustomizableContext addNode(String name); /** * *如果约束验证器将该值标记为无效,则添加要生成的新的ConstraintViolation.此对象来自ConstraintViolationBuilder实例的方法,并且约束违例生成器嵌套对象在此调用后返回IllegalStateException * @return ConstraintViolationBuilder 取出ConstraintValidatorContext实例 * * */ ConstraintValidatorContext addConstraintViolation(); } } }
-
ConstraintValidatorContext接口允许重新定义在约束无效时生成的默认约束消息,默认情况下,每个无效约束都会导致生成一个由ConstraintViolation对象表示的错误对象。该对象是根据默认约束消息模板构建的,该模板由约束声明和放置约束声明的上下文(bean,property, attribute)定义
-
ConstraintValidatorContext方法允许约束实现禁用默认的ConstraintViolation生成并创建一个或多个自定义约束。作为参数传递的非插值消息用于构造ConstraintViolation消息(将对其执行消息插值操作)。
-
默认情况下,ConstraintViolation上公开的Path表示托管约束的bean或属性的路径(有关更多信息,请参见第4.2节)。您可以使用约束违反构建器fluent API 将其指向该默认路径的子路径。
-
例子2.14 使用fluent API 去构建自定义约束违反示例
-
// 默认路径 context.buildConstraintViolationWithTemplate("this deatail is wrong") .addConstraintViolation(); //默认路径+“street" context.buildConstraintViolationWithTemplate("this detail is wrong") .addNode("street") .addConstraintViolation(); // 默认路径+”addresses["home"].country.name" context.buildConstraintViolationWithTemplate("this detail is wrong") .addNode("addresses") .addNode("country") .inInterable().atKey("home") .addNode("name") .addConstraintViolation();
-
如果调用disableDefaultConstraintViolation,则不添加任何自定义错误(使用错误生成器),并且如果约束无效,则引发ValidationException.
2.4.1、例子
-
例子2.15、ConstraintValidator 实现
-
/** * 检查文本是否在授权语法内 * */ public class SyntaxValidator implements ConstraintValidator<Syntax, String> { private Set<Format> allowedFormats; /** *根据定义时指定的元素来配置约束验证器 * @param constraint 约束定义 * */ public void initialize(Syntax constraint){ allowedFormats = new HashSet(Arrays.asList(constraint.value())); } /** *验证一个特定的值 *@return 如果指定的值不符合定义约束则返回false */ public boolean isValid(String value, ConstraintValidatorContext context){ if(value == null) return true; return allowedFormats.size() == 0 || (!Collections.disjoint(guessFormat(value), allowedFormats)); } Set<Format> guessFormats(String text){...} }
-
例子2.16、使用ConstraintValidatorContext
-
/** * 检查文本是否在授权语法内 *错误消息使用任意键: * - com.acme.constraint.Syntax.unknown 没检查到特定语法 * - com.acme.constraint.Syntax.unauthorized 如果语法没有授权 */ public class FineGrainedSyntaxValidator implements ConstraintValidator<Syntax, String> { private Set<Format> allowedFormats; /** *根据定义时指定的元素来配置约束验证器 * @param constraint 约束定义 * */ public void initialize(Syntax constraint){ allowedFormats = new HashSet(Arrays.asList(constraint.value())); } /** *验证一个特定的值 *@return 如果指定的值不符合定义约束则返回false */ public boolean isValid(String value, ConstraintValidatorContext context){ if(value == null) return true; Set<Format> guessedFormats = guessFormat(value); //关闭默认约束违例 context.disableDefaultConstraintViolation(); if(guessedFormats.size() == 0){ String unknown = "{com.acme.constraint.Syntax.unknown}"; context.buildConstraintViolationWithTemplate(unknown) .addConstraintViolation(); }else{ String unauthorized = "{com.acme.constraint.Syntax.unauthorized}"; context.buildConstraintViolationWithTemplate(unknown) .addConstraintViolation(); } return allowedFormats.size() == 0 || (!Collections.disjoint(guessFormat(value), allowedFormats)); } Set<Format> guessFormats(String text){...} }
-
根据检测到的约束违例的类型,默认错误消息将被禁用并由特定的错误消息代替。在这种情况下,在给定时间仅返回一个错误报告,但是约束验证实现可以返回多个错误报告。
2.5、ConstraintValidatorFactory (约束验证工厂类)
-
约束验证实现实例是由ConstraintValidatorFactory创建的。
-
/** * 根据其类实例化ConstraintValidator实例。ConstraintValidatorFactory不负责调用ConstraintValidator.initialize(java.lang.annotation.Annotation)方法 * * @author Dhanji R. Prasanna * @author Emmanuel Bernard * @author Hardy Ferentschik */ public interface ConstraintValidatorFactory{ /** * @param key 需要实例化约束验证器的类 * @return 返回具体实例化约束验证器 */ <T extends ConstraintValidator<?,?>> T getInstance(Class<T> key); }
-
Bean Validation提供程序实现提供的默认ConstraintValidatorFactory使用ConstraintValidator类public无参数构造函数,可以提供一个自定义的ConstraintValidatorFactory. 例如,它可以从约束实现中的依赖注入控制中受益。任何依赖于特定于实现的ConstraintValidatorFactory行为(约束注入,没有no-arg构造函数等)的约束实现都不被认为是可移植的,因此在走这条路之前需要格外小心
-
ConstraintValidatorFactory不应该缓存实例,因为可以在初始化方法中更改每个实例的状态。
-
如果在检索ConstraintValidator实例时工厂中发生了异常,则运行时异常将包装在ValidatorException中,如果工厂返回的实例为null,则引发ValidationException.