Hibernate Validator--创建自己的约束规则
尽管Bean Validation API定义了一大堆标准的约束条件, 但是肯定还是有这些约束不能满足我们需求的时候, 在这种情况下, 你可以根据你的特定的校验需求来创建自己的约束条件.
3.1. 创建一个简单的约束条件
按照以下三个步骤来创建一个自定义的约束条件
创建约束标注
实现一个验证器
定义默认的验证错误信息
3.1.1. 约束标注
让我们来创建一个新的用来判断一个给定字符串是否全是大写或者小写字符的约束标注. 我们将稍后把它用在第 1 章 开始入门中的类Car
的licensePlate字段上来确保这个字段的内容一直都是大写字母.
首先,我们需要一种方法来表示这两种模式( 译注: 大写或小写), 我们可以使用String
常量, 但是在Java 5中, 枚举类型是个更好的选择:
例 3.1. 枚举类型CaseMode
, 来表示大写或小写模式.
package com.mycompany; public enum CaseMode {
UPPER,
LOWER;
}
现在我们可以来定义真正的约束标注了. 如果你以前没有创建过标注(annotation)的话,那么这个可能看起来有点吓人, 可是其实没有那么难的 :)
例 3.2. 定义一个CheckCase的约束标注
package com.mycompany; import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target; import javax.validation.Constraint;
import javax.validation.Payload; @Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase { String message() default "{com.mycompany.constraints.checkcase}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};
CaseMode value(); }
一个标注(annotation) 是通过@interface
关键字来定义的. 这个标注中的属性是声明成类似方法的样式的. 根据Bean Validation API 规范的要求
message属性, 这个属性被用来定义默认得消息模版, 当这个约束条件被验证失败的时候,通过此属性来输出错误信息.
groups 属性, 用于指定这个约束条件属于哪(些)个校验组(请参考第 2.3 节 “校验组”). 这个的默认值必须是
Class<?>
类型到空到数组.payload
属性, Bean Validation API 的使用者可以通过此属性来给约束条件指定严重级别. 这个属性并不被API自身所使用.提示
通过payload属性来指定默认错误严重级别的示例
public class Severity {
public static class Info extends Payload {};
public static class Error extends Payload {};
} public class ContactDetails {
@NotNull(message="Name is mandatory", payload=Severity.Error.class)
private String name; @NotNull(message="Phone number not specified, but not mandatory", payload=Severity.Info.class)
private String phoneNumber; // ...
}这样, 在校验完一个
ContactDetails
的示例之后, 你就可以通过调用ConstraintViolation.getConstraintDescriptor().getPayload()
来得到之前指定到错误级别了,并且可以根据这个信息来决定接下来到行为.
除了这三个强制性要求的属性(message, groups 和 payload) 之外, 我们还添加了一个属性用来指定所要求到字符串模式. 此属性的名称value在annotation的定义中比较特殊, 如果只有这个属性被赋值了的话, 那么, 在使用此annotation到时候可以忽略此属性名称, 即@CheckCase(CaseMode.UPPER)
.
另外, 我们还给这个annotation标注了一些(所谓的) 元标注( 译注: 或"元模型信息"?, "meta annotatioins"):
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
: 表示@CheckCase 可以被用在方法, 字段或者annotation声明上.@Retention(RUNTIME)
: 表示这个标注信息是在运行期通过反射被读取的.@Constraint(validatedBy = CheckCaseValidator.class)
: 指明使用那个校验器(类) 去校验使用了此标注的元素.@Documented
: 表示在对使用了@CheckCase
的类进行javadoc操作到时候, 这个标注会被添加到javadoc当中.
提示
Hibernate Validator provides support for the validation of method parameters using constraint annotations (see 第 8.3 节 “Method validation”).
In order to use a custom constraint for parameter validation the ElementType.PARAMETER
must be specified within the @Target
annotation. This is already the case for all constraints defined by the Bean Validation API and also the custom constraints provided by Hibernate Validator.
3.1.2. 约束校验器
Next, we need to implement a constraint validator, that's able to validate elements with a @CheckCase
annotation. To do so, we implement the interface ConstraintValidator
as shown below:
例 3.3. 约束条件CheckCase
的验证器
package com.mycompany; import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> { private CaseMode caseMode; public void initialize(CheckCase constraintAnnotation) {
this.caseMode = constraintAnnotation.value();
} public boolean isValid(String object, ConstraintValidatorContext constraintContext) { if (object == null)
return true; if (caseMode == CaseMode.UPPER)
return object.equals(object.toUpperCase());
else
return object.equals(object.toLowerCase());
} }
ConstraintValidator
定义了两个泛型参数, 第一个是这个校验器所服务到标注类型(在我们的例子中即CheckCase
), 第二个这个校验器所支持到被校验元素到类型 (即String
).
如果一个约束标注支持多种类型到被校验元素的话, 那么需要为每个所支持的类型定义一个ConstraintValidator
,并且注册到约束标注中.
这个验证器的实现就很平常了, initialize()
方法传进来一个所要验证的标注类型的实例, 在本例中, 我们通过此实例来获取其value属性的值,并将其保存为CaseMode
类型的成员变量供下一步使用.
isValid()
是实现真正的校验逻辑的地方, 判断一个给定的String
对于@CheckCase
这个约束条件来说是否是合法的, 同时这还要取决于在initialize()
中获得的大小写模式. 根据Bean Validation中所推荐的做法, 我们认为null
是合法的值. 如果null
对于这个元素来说是不合法的话,那么它应该使用@NotNull
来标注.
3.1.2.1. ConstraintValidatorContext
例 3.3 “约束条件CheckCase的验证器” 中的isValid
使用了约束条件中定义的错误消息模板, 然后返回一个true
或者 false
. 通过使用传入的ConstraintValidatorContext
对象, 我们还可以给约束条件中定义的错误信息模板来添加额外的信息或者完全创建一个新的错误信息模板.
例 3.4. 使用ConstraintValidatorContext来自定义错误信息
package com.mycompany; import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> { private CaseMode caseMode; public void initialize(CheckCase constraintAnnotation) {
this.caseMode = constraintAnnotation.value();
} public boolean isValid(String object, ConstraintValidatorContext constraintContext) { if (object == null)
return true;
boolean isValid;
if (caseMode == CaseMode.UPPER) {
isValid = object.equals(object.toUpperCase());
}
else {
isValid = object.equals(object.toLowerCase());
}
if(!isValid) {
constraintContext.disableDefaultConstraintViolation();
constraintContext.buildConstraintViolationWithTemplate( "{com.mycompany.constraints.CheckCase.message}" ).addConstraintViolation();
}
return result;
} }
例 3.4 “使用ConstraintValidatorContext来自定义错误信息” 演示了如果创建一个新的错误信息模板来替换掉约束条件中定义的默认的. 在本例中, 实际上通过调用ConstraintValidatorContext
达到了一个使用默认消息模板的效果.
提示
在创建新的constraint violation的时候一定要记得调用addConstraintViolation
, 只有这样, 这个新的constraint violation才会被真正的创建.
In case you are implementing a ConstraintValidator
a class level constraint it is also possible to adjust set the property
path for the created constraint violations. This is important for the
case where you validate multiple properties of the class or even
traverse the object graph. A custom property path creation could look
like 例 3.5 “Adding new ConstraintViolation with custom property path”.
例 3.5. Adding new ConstraintViolation
with custom property path
public boolean isValid(Group group, ConstraintValidatorContext constraintValidatorContext) {
boolean isValid = false;
... if(!isValid) {
constraintValidatorContext
.buildConstraintViolationWithTemplate( "{my.custom.template}" )
.addNode( "myProperty" ).addConstraintViolation();
}
return isValid;
}
3.1.3. 校验错误信息
最后, 我们还需要指定如果@CheckCase
这个约束条件验证的时候,没有通过的话的校验错误信息. 我们可以添加下面的内容到我们项目自定义的ValidationMessages.properties
(参考 第 2.2.4 节 “验证失败提示信息解析”)文件中.
例 3.6. 为CheckCase
约束定义一个错误信息
com.mycompany.constraints.CheckCase.message=Case mode must be {value}.
如果发现校验错误了的话, 你所使用的Bean Validation的实现会用我们定义在@CheckCase
中message属性上的值作为键到这个文件中去查找对应的错误信息.
3.1.4. 应用约束条件
现在我们已经有了一个自定义的约束条件了, 我们可以把它用在第 1 章 开始入门中的Car
类上, 来校验此类的licensePlate属性的值是否全都是大写字母.
例 3.7. 应用CheckCase
约束条件
package com.mycompany; import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; public class Car { @NotNull
private String manufacturer; @NotNull
@Size(min = 2, max = 14)
@CheckCase(CaseMode.UPPER)
private String licensePlate; @Min(2)
private int seatCount;
public Car(String manufacturer, String licencePlate, int seatCount) { this.manufacturer = manufacturer;
this.licensePlate = licencePlate;
this.seatCount = seatCount;
} //getters and setters ... }
最后,让我们用一个简单的测试来检测@CheckCase
约束已经被正确的校验了:
例 3.8. 演示CheckCase
的验证过程
package com.mycompany; import static org.junit.Assert.*; import java.util.Set; import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory; import org.junit.BeforeClass;
import org.junit.Test; public class CarTest { private static Validator validator; @BeforeClass
public static void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
} @Test
public void testLicensePlateNotUpperCase() { Car car = new Car("Morris", "dd-ab-123", 4); Set<ConstraintViolation<Car>> constraintViolations =
validator.validate(car);
assertEquals(1, constraintViolations.size());
assertEquals(
"Case mode must be UPPER.",
constraintViolations.iterator().next().getMessage());
} @Test
public void carIsValid() { Car car = new Car("Morris", "DD-AB-123", 4); Set<ConstraintViolation<Car>> constraintViolations =
validator.validate(car); assertEquals(0, constraintViolations.size());
}
}
3.2. 约束条件组合
在例 3.7 “应用CheckCase约束条件”中我们可以看到, 类Car
的licensePlate属性上定义了三个约束条件. 在某些复杂的场景中, 可能还会有更多的约束条件被定义到同一个元素上面, 这可能会让代码看起来有些复杂, 另外, 如果在另外的类里面还有一个licensePlate属性, 我们可能还要把这些约束条件再拷贝到这个属性上, 但是这样做又违反了 DRY 原则.
这个问题可以通过使用组合约束条件来解决. 接下来让我们来创建一个新的约束标注@ValidLicensePlate
, 它组合了@NotNull
, @Size
和 @CheckCase
:
例 3.9. 创建一个约束条件组合ValidLicensePlate
package com.mycompany; import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target; import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; @NotNull
@Size(min = 2, max = 14)
@CheckCase(CaseMode.UPPER)
@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {})
@Documented
public @interface ValidLicensePlate { String message() default "{com.mycompany.constraints.validlicenseplate}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
我们只需要把要组合的约束标注在这个新的类型上加以声明 (注: 这正是我们为什么把annotation types作为了@CheckCase
的一个target). 因为这个组合不需要额外的校验器, 所以不需要声明validator属性.
现在, 在licensePlate属性上使用这个新定义的"约束条件" (其实是个组合) 和之前在其上声明那三个约束条件是一样的效果了.
例 3.10. 使用ValidLicensePlate
组合约束
package com.mycompany; public class Car { @ValidLicensePlate
private String licensePlate; //... }
The set of ConstraintViolations
retrieved when validating a Car
instance will contain an entry for each violated composing constraint of the @ValidLicensePlate
constraint. If you rather prefer a single ConstraintViolation
in case any of the composing constraints is violated, the @ReportAsSingleViolation
meta constraint can be used as follows:
例 3.11. @ReportAsSingleViolation
的用法
//...
@ReportAsSingleViolation
public @interface ValidLicensePlate { String message() default "{com.mycompany.constraints.validlicenseplate}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Hibernate Validator--创建自己的约束规则的更多相关文章
- Java Hibernate Validator
Hibernate Validator是Hibernate提供的一个开源框架,使用注解方式非常方便的实现服务端的数据校验. 官网:http://hibernate.org/validator/ hib ...
- Hibernate Validator Engine的用法
一.引入架包 maven地址 点击即可. <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-v ...
- spring boot中使用javax.validation以及org.hibernate.validator校验入参
这里springboot用的版本是:<version>2.1.1.RELEASE</version> 自带了hibernate.validator,所以不用添加额外依赖 1.创 ...
- SpringMVC--使用hibernate validator数据校验
JSR 303 Spring3开始支持JSR 303 验证框架,JSR303是Java为Bean数据合法性校验所提供的标准框架.JSR 303 支持XML和注解风格的验证,通过在Bean属性上标注类似 ...
- Hibernate自动创建表
只要在hibernate.cfg.xml添加这句话,就可以自动生成数据表 <property name="hibernate.hbm2ddl.auto">update& ...
- Hibernate Validator验证标签说明
Hibernate Validator是JSR-303的一个实现. 在FormBean里添加Hibernate Validator的注解,与定义一个校验类的做法相比.注解更加简洁.灵活. Bean V ...
- 非WEB项目中引入Hibernate Validator
前言: 网上一些朋友分享了关于hibernate-validator的使用方法,但是不是缺少关联库信息,就是提供的参考代码中缺少自定类. 希望我这一篇博客能够让你顺利的跑出预期的结果. 如果有错,可以 ...
- Hibernate Validator
http://docs.jboss.org/hibernate/validator/4.2/reference/zh-CN/html_single/#example-group-interfaces
- spring boot 1.4默认使用 hibernate validator
spring boot 1.4默认使用 hibernate validator 5.2.4 Final实现校验功能.hibernate validator 5.2.4 Final是JSR 349 Be ...
随机推荐
- 低秩近似 low-rank approximation
- php, tp5, 选中导航菜单
1. 首先定义一个函数: function nav_select($navindex){ $nav_arr = [ 1 => ['index',], 2 => ['mei',], 3 =& ...
- 【python】-- web开发之jQuery
jQuery jQuery 是一个 JavaScript 函数库,jQuery库包含以下特性(HTML 元素选取.HTML 元素操作.CSS 操作.HTML 事件函数.JavaScript 特效和动画 ...
- linux c编程:进程控制(四)进程关系
每一个进程除了有一个进程ID外,还属于一个进程组. 进程组是一个或多个进程的集合,通常情况下,他们是在同一作业中结合起来的,同一进程组的个进程接受来自同一终端的各种信号. 每一个进程组有一个唯一的进 ...
- linux系统环境下搭建coreseek(+mmseg3) (good)
1.下载并解压coreseek软件,操作命令如下: wget http://www.coreseek.cn/uploads/csft/3.2/coreseek-3.2.14.tar.gz 说明:文件下 ...
- || and && 理解
逻辑或(||): 只要第一个值的布尔值为false,那么永远返回第二个值. 逻辑或属于短路操作,第一个值为true时,不再操作第二个值,且返回第一个值. 逻辑与(&&): 只要第一个值 ...
- iOS swift 语句只能写在函数体内
1. 语句只能在函数体内: eg 因为我写在playground里面没报错 我直接放在这个位置就报错了 在这个.swift 文件里面 print 应该写在func 等方法(函数)里面 其他语句 ...
- Python OOP(3) staticmethod和classmethod统计实例
staticmethod 统计实例 #!python2 #-*- coding:utf-8 -*- class c1: amount_instance=0 def __init__(self): c1 ...
- ubuntu14.04搭建gitlab
以下内容来自:https://mirror.tuna.tsinghua.edu.cn/help/gitlab-ce/ (清华大学开源软件镜像站)可以直接移步上面的网站.这里做个笔记,也是为了记录一下 ...
- Html标签使用——文字、列表、表格、超链接
注:文章来源于传智播客毕向东老师使用课件和网络.整理学习如下: 一.Html内容 1. Html就是超文本标记语言的简写,是最基础的网页语言. 2. Html是通过标签来定义的语言,代码都是由 ...