数据校验是任何一个应用程序都会用到的功能,无论是显示层还是持久层. 通常,相同的校验逻辑会分散在各个层中, 这样,不仅浪费了时间还会导致错误的发生(译注: 重复代码). 为了避免重复, 开发人员经常会把这些校验逻辑直接写在领域模型里面, 但是这样又把领域模型代码和校验代码混杂在了一起, 而这些校验逻辑更应该是描述领域模型的元数据.
Jsr303
JSR 303 - Bean Validation - 为实体验证定义了元数据模型和API. 默认的元数据模型是通过Annotations来描述的,但是也可以使用XML来重载或者扩展. Bean Validation API 并不局限于应用程序的某一层或者哪种编程模型
Hibernate Validator是参照jsr实现的。
第一个是示例
首先导入 validation-api.jar和 jboss-logging包
创建一个model类(UserModel)
UserModel中有三个约束:①用户名不能为空 ②密码长度位8~12 ③年龄大于等于18
| import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size;
public class UserModel { @NotNull private Stringusername;
@NotNull @Size(min=8, max=12) private Stringpassword;
@Min(18) private int age; //省略getter, setter方法 } |
| @NotNull, @Size, @Min 就是所属的约束性标注 |
测试类
| public class ValidationTest { private static Validator validator;
@BeforeClass public static void setUp() { //创建工厂获取验证器 ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } @Test public void testValidationUserModel() { UserModel user = new UserModel(null,"12345678", 4); Set<ConstraintViolation<UserModel>> constraintViolations = validator.validate(user); Iterator<ConstraintViolation<UserModel>> ite = constraintViolations.iterator(); while(ite.hasNext()) { System.out.println(ite.next().getMessage()); } } } |
测试结果:
不能为null
最小不能小于18
在setUp方法中,我们通过ValidatorFactory得到了一个Validator的实例,Validator是线程安全的, 并且可以重复利用
Validate方法会返回一个set的ConstraintViolation的实例的集合, 我们可以通过遍历它来查看有哪些验证错误. 前面三个测试用例显示了一些预期的校验约束
在testValidationUserModel中, 用户名为空, 并且年龄也小于18
如果一个对象没有校验出问题的话, 那么validate会返回一个空的set对象
定义约束
Beanvalidation的约束是通过java注解来标注的, 并且分为三种不同的注解类型
一.字段级 ( field level)约束
当约束被定义在字段上的时候, 这个字段的值是通过字段访问策略来获取并验证的. 也就是说Bean Validation的实现者会直接访问这个实例变量而不会调用属性的访问器(getter) 即使这个方法存在.
Tip:这个字段的访问级别( private, protected 或者 public) 对此没有影响
Tip:静态字段或者属性是不会被校验的
二.属性级别约束
和字段级别约束的区别是它的约束是定义在属性级别上的
并且只能定义在getter上面, 不能定义在setter上
三.类级别约束
最后, 一个约束也能够被放在类级别上. 当一个约束被标注在一个类上的时候,这个类的实例对象被传递给ConstraintValidator. 当需要同时校验多个属性来验证一个对象或者一个属性在验证的时候需要另外的属性的信息的时候, 类级别的约束会很有用.
约束继承
如果要验证的对象继承某个父类或者实现了某个接口, 那么定义在父类或者接口中的约束会在验证这个对象的时候被自动加载。
对象图
BeanValidation Api不仅能够用来校验单个实例对象,还能够用来校验完整的对象图
要使用这个功能,只需要在一个有关联关系的字段或者属性上标注@Valid. 这样,如果一个对象被校验,那么它的所有的标注了@Valid的关联对象都会被校验.
关联校验也适用于集合类型的字段
如果标注了@Valid, 那么当主对象被校验的时候, 这些集合对象中的元素也会被校验
Tip:对象图校验的时候会忽略null, 所以一般需要加上@NotNull和@Valid
获取一个Validator的实例
ValidatorFactoryfactory = Validation.buildDefaultValidatorFactory();
Validatorvalidator = factory.getValidator();
Validator中的方法
Validator中有三个方法能够被用来校验整个实体对象或者实体对象中的属性
这三个方法都会返回一个Set< ConstraintViolation>,如果整个验证过程没有发现问题的话,那么这个set是空的, 否则, 每个违反约束的地方都会被包装成一个ConstraintViolation的实例然后添加到set当中
Validate
使用validate方法对一个给定的实体对象中定义的所有约束条件进行检验
validateProperty
通过validateProperty可以对一个给定实体对象的单个属性进行校验
| @Test public void testValidationUserModelProperty() { UserModel user = new UserModel(null,"12345678", 4); Set<ConstraintViolation<UserModel>> constraintViolations = validator.validateProperty(user,"age"); Iterator<ConstraintViolation<UserModel>> ite = constraintViolations.iterator(); while(ite.hasNext()) { System.out.println(ite.next().getMessage()); } } |
validateValue
通过validateValue()方法, 你能够校验如果把一个特定的值赋给一个类的特定属性
| @Test public void testValidationUserModelValue() { UserModel user = new UserModel(null,"12345678", 4); Set<ConstraintViolation<UserModel>> constraintViolations = validator.validateValue(UserModel.class,"age", 10); Iterator<ConstraintViolation<UserModel>> ite = constraintViolations.iterator(); while(ite.hasNext()) { System.out.println(ite.next().getMessage()); } } |
ConstrainViolation中的方法
getMessage():获取经过翻译的校验错误信息
getMessageTemplate():获取错误信息模板
getRootBean():获取被校验的根实体对象
getRootBeanClass():获取被校验的根实体类
getLeafBean():如果约束是添加在一个Bean上的,那么则返回这个bean的实例,如果约束是定义一个属性上的,则返回这个属性所属的bean的实例对象
getPropertyPath():从被验证的根对象到被验证的属性的路径
getInvalidValue():导致校验失败的值
getConstraintDescriptior():导致校验失败的约束定义
验证失败提醒信息解析
如果在校验的时候,这个约束条件没有通过,那么你配置的MessageInterpolator会被用来当做解析器来解析这个约束中定义的消息模板。
用\来进行转义
如果默认的消息解析器不能够满足你的需求,那么你可以创建CalidatorFactory的时候,将其替换为一个你自定义的MessageInterpolator
校验组
校验组能够让你在验证的时候选择应用哪些约束条件,这样在某些条件下就可以个性化的进行验证。
如果没有明确指定某个约束条件属于哪个组, 那么它将归类于默认组
校验的注解中都有一个属性group,这个属性可以指定该校验属于哪个校验组
validator.validate(person, PersonAgeChecks.class).iterator()
validate的第二个参数就是指定校验组,如果只有一个参数,便默认校验组为default
tip: 如果一个校验组有一个约束条件没有通过验证的话,那么此约束后面的都不会再进行校验
hibernate-validation中有一些公共的约束,也提供了一些自定义的约束
公共的约束
@AssertFalse @AssertTrue 检验boolean类型的值
@DecimalMax @DecimalMin 限定被标注的属性的值的大小
@Digits(intege=,fraction=) 限定被标注的属性的整数位数和小数位数
@Future检验给定的日期是否比现在晚
@Past 校验给定的日期是否比现在早
@Max检查被标注的属性的值是否小于等于给定的值
@Min检查被标注的属性的值是否大于等于给定的值
@NotNull检验被标注的值不为空
@Null 检验被标注的值为空
@Pattern(regex=,flag=) 检查该字符串是否能够在match指定的情况下被regex定义的正则表达式匹配
@Size(min=,max=) 检查被标注元素的长度
@Valid递归的对关联的对象进行校验
自定义的约束
@CreditCardNumber
@Email校验这个字符串是否为有效的邮箱地址
@Length(min=,max)校验字符串的长度
@NotBlank 校验字符不为null并且trim之后的长度是大于0的
@NotEmpty 被标注的元素不能为null,且不能没有成员
@Range(min=,max=) 检查被标注的元素的值是否位于范围内(闭区间)
//..
创建自己的约束规则
创建一个自定义的约束条件的步骤:
一.创建约束标注
二.实现一个验证器
三.定义默认的验证错误信息
① 约束标注
创建一个用来判断给定字符串是否全是大写或者小写字符的约束标注
| public enum CaseMode { UPPER, LOWER; } public class Severity { public static class Info implements Payload{}; public static class Error implements Payload{}; }
|
Serverity用来指定错误级别, 可以根据ConstraintViolation.getConstraintDescriptor()来得到之前指定的错误级别。
| @Target( { METHOD,FIELD,ANNOTATION_TYPE }) @Retention(RUNTIME) @Constraint(validatedBy = CheckCaseValidator.class) @Documented public @interface CheckCase { /** * 这个属性被用来定义默认消息模板哈哈 * @return */ String message() default"{org.chuninsane.checkcase}"; /** * 用来指定这个约束条件属于那些校验组 * @return */ Class<?>[] groups() default {}; /** * 这个属性不是API要求的,用来给约束条件指定严重级别 * @return */ Class<? extends Payload>[] payload()default {}; /** * 如果只有这个属性的话,可以忽略该属性的名称 * eg:@CaseCheck(CaseMode.UPPER) * @return */ CaseMode value(); } |
我们给这个Annotation注解一些元标注
@Target({METHOD, FIELD, ANNOTATION_TYPE }): 表示@CheckCase 可以被用在方法, 字段或者annotation声明上.
@Retention(RUNTIME):表示这个标注信息是在运行期通过反射被读取的.
@Constraint(validatedBy= CheckCaseValidator.class): 指明使用那个校验器(类) 去校验使用了此标注的元素.
@Documented:表示在对使用了@CheckCase的类进行javadoc操作到时候, 这个标注会被添加到javadoc当中.
② 约束校验器
| /** * 检查字符串大小写的验证器 * ConstraintValidator中的两个泛型参数,第一个是这个校验器匹配的标注类型 * 第二个参数为校验器支持被校验元素的类型 * @authorchuninsane * */ public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> { private CaseModemode; /** * 进行初始化 */ @Override public void initialize(CheckCase checkCaseAnnotation) { this.mode = checkCaseAnnotation.value(); } /** * 进行验证 */ @Override public boolean isValid(String obj, ConstraintValidatorContext constraintContext) { //根据Bean Validation中所推荐的做法,我们认为null是合法的值 if(obj ==null) return true;
if(mode == CaseMode.UPPER) return obj.equals(obj.toUpperCase()); else return obj.equals(obj.toLowerCase()); } } |
ConstraintValidatorContext
在isValid中可以通过传入的ConstraintValidatorContext对象,给约束条件中定义的错误信息模板来额外添加的信息或者完全创建一个新的错误信息模板
| if(mode == CaseMode.UPPER) isValid = obj.equals(obj.toUpperCase()); else isValid = obj.equals(obj.toLowerCase()); if(!isValid) { constraintContext.disableDefaultConstraintViolation(); constraintContext.buildConstraintViolationWithTemplate("{org.chuninsane.entity.CheckCase.message}").addConstraintViolation(); } |
Tip: 在创建新的constraint violation的时候一定要记得调用addConstraintViolation, 只有这样, 这个新的constraint violation才会被真正的创建
Tip:切勿忘记在classpath下创建ValidationMessages.properties文件, 这个文件用来配置消息信息, 文件中加入一行配置
org.chuninsane.entity.CheckCase.message=Case mode must be{value}
校验错误信息
在Person类的name属性中加入@CaseCheck(CaseMode.UPPER)
具体的测试类:
| @Test public void testCaseModeValidator() { Person person = new Person(); person.setName("Chuninsane"); person.setAge(20); Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person); Iterator<ConstraintViolation<Person>> ite = constraintViolations.iterator(); while(ite.hasNext()) { System.out.println(ite.next().getMessage()); } } |
测试结果:
Case mode must be UPPER
约束条件组合
如果我们在应用中对一个属性定义多个约束条件,在有些复杂的情况下,可能还需要更多的约束条件被定义到同一个元素上面,如果还有一个同样的元素,这将造成大量的重复。
此时可以创建一个约束条件组合ValidLicensePlate
| @NotNull @Size(min = 2, max = 14) @CheckCase(CaseMode.UPPER) @Target( { METHOD,FIELD,ANNOTATION_TYPE }) @Retention(RUNTIME) @Constraint(validatedBy = {}) @Documented public @interface ValidGroup { String message() default"this is a validate group"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload()default {}; } |
这个组合不需要额外的校验器, 所以@Constraint中设置为空就行
XML配置映射约束
我们可以使用validation.xml来对Hibernate Validator进行配置, ValidationFactory在初始化的时候会在类路径下寻找此文件, 如果找到机会应用其中定义的配置信息。
Validation.xml
我们可以使用validation.xml来对Hibernate Validator进行配置。
<constraint-mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.0.xsd"
xmlns="http://jboss.org/xml/ns/javax/validation/mapping">
<default-package>org.chuninsane.entity</default-package>
<bean class="Car" ignore-annotations="true">
<field name="manufacturer">
<constraint annotation="javax.validation.constraints.NotNull"/>
</field>
<field name="licensePlate">
<constraint annotation="javax.validation.constraints.NotNull"/>
</field>
<field name="seatCount">
<constraint annotation="javax.validation.constraints.Min">
<element name="value">2</element>
</constraint>
</field>
<field name="driver">
<valid/>
</field>
<getter name="passedVehicleInspection" ignore-annotations="true">
<constraint annotation="javax.validation.constraints.AssertTrue">
<message>The car has to pass the vehicle inspection first</message>
<groups>
<value>CarChecks</value>
</groups>
<element name="max">10</element>
</constraint>
</getter>
</bean>
<bean class="RentalCar" ignore-annotations="true">
<class ignore-annotations="true">
<group-sequence>
<value>RentalCar</value>
<value>CarChecks</value>
</group-sequence>
</class>
</bean>
<constraint-definition annotation="org.mycompany.CheckCase" include-existing-validator="false">
<validated-by include-existing-validators="false">
<value>org.chuninsane.validator.CheckCaseValidator</value>
</validated-by>
</constraint-definition>
</constraint-mappings>
Tip:每个实体类只能在所有的XML映射文件中被定义一次, 否则会抛出异常。
Configuration和ValidatorFactory
Validation类提供了三种方法来创建一个Validator的实例
方法一:
ValidatorFactoryfactory = Validation.buildDefaultValidatorFactory();
Validatorvalidator = factory.getValidator();
方法二:
Configuration<?>config = Validation.byDefaultProvider().configure();
Config.messageInterpolator(newMyMessageInterpolator())
.traverableResolver(newMyTraversableResolver())
.constraintValidatorFactory(newMyConstraintValidatorFactory());
ValidatorFactoryfactory = config.buildValidatorFactory();
Validatorvalidator = factory.getValidator();
方法三:
Configuration<?>config = Validation.byProvider(HibernateValidator.class.configure();)
Config.messageInterpolator(newMyMessageInterpolator())
.traverableResolver(newMyTraversableResolver())
.constraintValidatorFactory(newMyConstraintValidatorFactory());
ValidatorFactoryfactory = config.buildValidatorFactory();
Validatorvalidator = factory.getValidator();
创建出来的Validator实例是线程安全的,可以缓存下来
ValidationProviderResolver
如果Java Service Provider 机制在你的环境中不能够正常工作,或者你有特别的classloader设置的话, 你也可以提供一个自定义的ValidationProviderResolver
MessageInterpolator
验证失败提示信息解析
ResourceBundleLocator
一个普遍的需求是你可能需要为错误消息解析指定你自己的
resourcebundles. ResourceBundleMessageInterpolator 是Hibernate Validator中默认的MessageInterpolator的实现,它默认情况下是通过ResouceBundle.getBundle来获取resource bundle的
TraversableResolver
它的设计目的是在某些情况下,我们可能不应该去属性的状态。最典型的情况就是一个延迟加载的属性或者与JPA中涉及到关联关系的时候,当验证这两种情况的属性时,看能需要出发一次对数据库的查询。Bean Validation正式通过TravesableResolver接口控制能否访问某一个属性的
集成IDE的时候需要的包
validation-api.jar
hibernate-validator.jar
hibernate-validator-annotation-processor.jar
本文详细介绍了Hibernate Validator在数据校验中的应用,包括JSR 303规范,字段级、属性级和类级别约束,对象图校验,自定义约束,校验组,以及如何创建和配置Validator。通过实例展示了如何在实体类中定义约束,并进行了测试,揭示了验证失败后的错误信息解析和处理策略。
1263

被折叠的 条评论
为什么被折叠?



