Bean Validation规范
以下内容转载自:https://www.ibm.com/developerworks/cn/java/j-lo-beanvalid/
Bean Validation规范介绍
JSR303 规范(Bean Validation 规范)提供了对 Java EE 和 Java SE 中的 Java Bean 进行验证的方式。该规范主要使用注解的方式来实现对 Java Bean 的验证功能,并且这种方式会覆盖使用 XML 形式的验证描述符,从而使验证逻辑从业务代码中分离出来。javax.validation是JSR303规范,而hibernate-validator是则是规范的具体实现。
POM文件中引入hibernate-validator即可使用JSR303规范:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.0.Final</version>
</dependency>
或者普通工程添加相关jar包:hibernate-validator 、jboss-logging 、 validation-api这些包吧.
入门案例:
先将最基本的Bean Validation封装成一个简单的工具类,方便使用,案例学习。
Validation API简单封装的ValidationUtils.java
public class ValidationUtils { public static Validator getValidator(){
return validator;
} static Validator validator;
static{
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator=validatorFactory.getValidator();
}
}
定义我们要校验的Java Bean:
Employee.java类
@Setter
@Getter
@NoArgsConstructor //lombok注解 节约篇幅 代码整洁
public class Employee { @NotNull(message = "员工姓名不能为空")
@Size(min = 1,max = 10,message = "员工名字长度必须在10个字母以内")
private String name;
@NotNull(message = "员工ID不能为空")
private Integer id;
}
使用方式:
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("lvbinbin6666");
Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
for (ConstraintViolation constraintViolation:violations) {
System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
System.out.println("报错原因为:"+constraintViolation.getMessage());
}
}
查看结果,可以看到这就是Bean Validation的简单使用,基于注解方式还是很方便的。
验证流程
完成Java Bean的验证流程通常分为四个步骤:
1.约束注解定义
2.约束验证器验证规则定义
3.约束注解声明
4.约束验证流程
自定义实现JSR303规范----@NotEmpty
Bean Vadalition中并没有字符串为空的注解验证,Hibernate-validator扩展了@NotEmpty来校验 字符串不为空 ,自己实现一个字符串不为空的校验规则
按照上面的流程,首先需要约束注解的定义,仿照@NotNull复制一个NotEmpty;
import static java.lang.annotation.ElementType.*; //静态导包 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) //注解应用的目标类型
@Retention(RetentionPolicy.RUNTIME) //注解应用时期
@Documented
@Constraint(validatedBy = {NotEmptyValidator.class}) //注解关联的验证器,JSR303的注解
public @interface NotEmpty {
String message() default ""; //验证时输出信息 Class<?>[] groups() default { }; //验证时所属的组别 Class<? extends Payload>[] payload() default {};
}
其中ValidatedBy就是自定义的验证器的指向。第二步编写自定义验证器NotEmptyValidator
public class NotEmptyValidator implements ConstraintValidator<NotEmpty,String> {
@Override
public void initialize(NotEmpty constraintAnnotation) { } @Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(value==null) return false;
else if(value.trim().length()==0) return false;
else
return true;
}
}
第三步声明约束注解(代码为增量形式,重复的就忽略了)
@NotEmpty(message = "员工职位不能为空")
private String job;
第四步验证约束流程
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("");
employee.setId(18);
employee.setJob("");
Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
for (ConstraintViolation constraintViolation:violations) {
System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
System.out.println("报错原因为:"+constraintViolation.getMessage());
}
}
验证结果就不贴了,在自定义验证器中输出信息,发现确实调用了自定义的验证规则;我们并没有对@NotEmpty添加到Validator中,只是在实体类上声明了,就会找到这个注解,进而根据Constraint注解上的validatedBy,找到自定义的验证器,从而完成验证流程!
Bean Validation内嵌的约束注解
非空性验证两个: @NotNull 不为空 ; @Null 为空
布尔型验证两个:@AssertTrue 布尔值为true ; @AssertFalse 布尔值不为空
日期类型验证两个:@Past 日期必须为过去的日期 ; @Future 日期必须为未来某个日期
正则表达式验证一个:@Pattern
数值类型验证若干:@Min String或Number类型大于等于该值 ; @Max String或Number类型小于等于该值 这两种类型的值不支持小数校验
@DecimalMin String或Number类型大于等于该值,支持小数 ; @DecimalMax String或Number类型小于等于该值,支持小数
集合验证类型一个:@Size String、Array、Collection、Map长度类型在设定范围内
多值约束问题
Bean Validation的一个特性:多值约束。
@NotNull等内嵌注解最下面都会有@interface List这样一个注解,就是用来实现多值约束。 补充说明:一个注解无法再同一个位置标注两次,编译报错Duplicate Annotaion,所以就有了内部注解,此外还可以通过@Repeatable容器的概念来实现多个注解标注。
举个栗子,我们需要判断一个字符串包含多个子串,类似地一个数组、集合、map包含这种属性也是一样的;
按照上面流程,定义约束注解 @Dictionary
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {DictionaryValidator.class})
public @interface Dictionary {
String skillValue(); String message() default ""; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default {}; @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
Dictionary[] value();
}
}
自定义约束验证器规则编写:
public class DictionaryValidator implements ConstraintValidator<Dictionary,String> {
String skillValue=null;
@Override
public void initialize(Dictionary constraintAnnotation) {
skillValue=constraintAnnotation.skillValue();
} @Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(value==null){
return false;
} else if(value.trim().length()==0){
return false;
}else if(value.contains(skillValue)){
return true;
}
return false;
}
}
第三步声明注解,和@Repeatable有异曲同工之妙
@Dictionary.List(value = {
@Dictionary(message = "该员工不会java",skillValue = "java"),
@Dictionary(message = "该员工不会vue",skillValue = "vue")
})
private String skill;
第四步 验证约束
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("lvbinbin");
employee.setId(18);
employee.setJob("");
employee.setSkill("java");
Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
for (ConstraintViolation constraintViolation:violations) {
System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
System.out.println("报错原因为:"+constraintViolation.getMessage());
}
}
得出结论:达到了我们的目的,校验skill属性包含 java vue子字符串,其实@Pattern编写正则表达式就可以达到这样的目的,但是集合 数组等类型多值判断时就需要多值校验了,还是有用武之处的!
组合约束
假设现在校验员工身高,正常身高假设在1米以上3米以下,@Min(value=100) @Max(value=300)可能就完成这样一个简单的校验规则。Bean Validation新特性,组合校验。
import static java.lang.annotation.ElementType.*;
@NotNull(message = "如实禀报身高")
@Min(message = "正常人身高在1米以上",value = 100)
@Max(message = "正常人身高在3米以下",value = 300)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) //注解应用的目标类型
@Retention(RetentionPolicy.RUNTIME) //注解应用时期
@Documented
@Constraint(validatedBy = {})
public @interface Height {
String message() default ""; //验证时输出信息 Class<?>[] groups() default { }; //验证时所属的组别 Class<? extends Payload>[] payload() default {};
}
声明注解@Height
@Height
private Integer height;
校验流程
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("lvbinbin");
employee.setId(18);
employee.setJob("");
employee.setSkill("java vue");
employee.setHeight(183);
Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
for (ConstraintViolation constraintViolation:violations) {
System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
System.out.println("报错原因为:"+constraintViolation.getMessage());
}
}
补充记录:测试时候发现,@Height注解报错提示信息没有用,因为组合的注解没有通过,不是@Height注解校验不通过,可以再自定义约束验证器,这样校验不通过就能抛出@height中的message;
Bean Validation验证规则流程
调用 Validator.validate(beanInstance) 方法后,Bean Validation 会查找在 beanInstance上所有的约束声明(注解式),对每一个约束调用对应的约束验证器进行验证,由约束验证器的 isValid 方法产生,如果该方法返回 true,则约束验证成功,否则验证失败。验证失败生成约束违规对象(ConstraintViolation 的实例)并放到约束违规列表中。验证完成后所有的验证失败信息均能在该列表中查找并输出。
条件:静态方法、字段无法约束验证;可以在类或者接口上使用约束验证,它将对该类或实现该接口的实例进行状态验证;
级联验证方式
对象之间存在级联关系,A类存在属性B,对A校验的同时,如果需要对B完成校验,在B上添加@Valid即可;
举个栗子,假设Address存在属性Employee,在对Address实例校验时,在Employee上标注@Valid;
@Setter
@Getter
public class Address {
@Valid
Employee employee;
@NotNull
String location;
}
校验流程
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("lvbinbin");
employee.setId(18);
employee.setJob("");
employee.setSkill("java vue");
employee.setHeight(95);
Address address = new Address();
address.setEmployee(employee);
Set<ConstraintViolation<Address>> violations = ValidationUtils.getValidator().validate(address);
for (ConstraintViolation constraintViolation:violations) {
System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
System.out.println("报错原因为:"+constraintViolation.getMessage());
}
}
输出结果: 注释掉@Valid,employee属性就不会校验
Bean Validation注解生效
Bean Validation的注解可以在属性上 、在getter方法上生效,至于为什么在setter方法上没法生效 这个可能要成为谜题了;
测试getter上生效
private Date birth;
public void setBirth(String date) throws ParseException {
this.birth=new SimpleDateFormat("yyyy/MM/dd").parse(date);
}
@Past
public Date getBirth(){
return birth;
}
验证流程:
public static void main(String[] args) throws ParseException {
Employee employee = new Employee();
employee.setName("lvbinbin");
employee.setId(18);
employee.setJob("");
employee.setSkill("java vue");
employee.setHeight(183);
employee.setBirth("9102/12/31");
Set<ConstraintViolation<Employee>> violations = ValidationUtils.getValidator().validate(employee);
for (ConstraintViolation constraintViolation:violations) {
System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
System.out.println("报错原因为:"+constraintViolation.getMessage());
}
}
验证结果:
组
Bean Validation中提出了组的概念,这点和Jackson中的@JsonView很类似,我只展示我需要的属性;而Bean Validation中组,给每个校验属性分组,我只校验我指定的组中包含的属性,我不显示的指定组名,那就校验默认的组,Default组;
使用说明一:默认不指定组的话,下面Validator.validate(beanInstance)和Validator.validate(beanInstance,Default.class)这两个输出是一样的,证明了默认组别叫Default; 这里的Default是javax.validation包中的,其他很多地方有这个类,不要到错了!
@Setter
@Getter
public class User { @NotNull
private String groupfieldA1;
@NotNull
private String groupfieldA2;
@NotNull
private String groupfieldB1;
@NotNull
private String groupfieldB2; public static void main(String[] args) {
User user = new User();
//Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user);
Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, Default.class);
for (ConstraintViolation constraintViolation:violations) {
System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
System.out.println("报错原因为:"+constraintViolation.getMessage());
}
}
}
使用说明二:
组别一定要采用接口定义,组别不是接口会抛出异常:javax.validation.ValidationException: HV000045: A group has to be an interface
组的接口不一定要重新再定义,哪怕使用已有的接口也没有问题,我觉得只是作为标识来使用;
public static interface groupA{}
@NotNull(groups = {groupA.class})
private String groupfieldA1;
这样通过Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, groupA.class)校验,就只会针对groupA组的属性校验,即属性groupfieldA1
使用说明三:
组接口继承,属性也会继承被校验; 这点类比JsonView
public static interface groupA{}
public static interface groupB extends groupA{}
@NotNull(groups = {groupA.class})
private String groupfieldA1;
@NotNull(groups = {groupB.class})
private String groupfieldA2;
这样通过Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, groupB.class)校验,组别B的属性校验,组别A的属性也会被校验;
组序列 @GroupSequence
Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, groupA.class,groupB.class)支持多个组进行校验,而且组与组之间互不影响,校验组别顺序只与输入的组别顺序级groupA>groupB有关,组序列为了满足组与组之间互相存在影响而出现,保证了顺序以及避免不必要的校验(加入校验B依赖于校验A,校验A都没通过,校验B就没必要校验了)
@Setter
@Getter
public class User {
public static interface groupA{}
public static interface groupB{} @GroupSequence(value = {groupA.class,groupB.class})
public static interface group {} @NotNull(groups = {groupA.class})
private String groupfieldA1;
@NotNull(groups = {groupB.class})
private String groupfieldA2;
@NotNull
private String groupfieldB1;
@NotNull
private String groupfieldB2; public static void main(String[] args) {
User user = new User();
//Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user);
Set<ConstraintViolation<User>> violations = ValidationUtils.getValidator().validate(user, group.class);
for (ConstraintViolation constraintViolation:violations) {
System.out.println("当前有问题属性为:"+constraintViolation.getPropertyPath().toString());
System.out.println("报错原因为:"+constraintViolation.getMessage());
}
}
}
输出截图: @GroupSequence保证了校验的顺序,以及存在依赖关系时的校验,第一个都没有通过,后续不再校验
Bean Validation接口规范
MessageInterpolator接口:消息解析器,用来将验证过程中失败的消息以可读的方式传递给调用者; Bean Validation规范提供一个默认实现,configuration.getDefaultMessageInterpolator();
用户自定义消息解析器只需要实现MessageInterpolator接口;
Bean Validation 规范的输出消息默认从类路径下的 ValidationMessage.properties 文件中读取,用户也可以在约束注解声明的时候使用 message 属性指定消息内容。
Configuration接口:收集上下文环境中的配置信息,主要用来计算如何给定正确的 ValidationProvider,并将其委派给 ValidatorFactory 对象。
参考文档
BeanValidation : https://www.ibm.com/developerworks/cn/java/j-lo-beanvalid/
Bean Validation规范的更多相关文章
- Spring3.1 对Bean Validation规范的新支持(方法级别验证)
上接Spring提供的BeanPostProcessor的扩展点-1继续学习. 一.Bean Validation框架简介 写道Bean Validation standardizes constra ...
- Java bean validation 规范与参考实现
1.Apache Bval 依赖包:validation-api-1.1.0.Final.jar org.apache.bval.bundle-1.1.1.jar bval-core-1.1.1.ja ...
- JSR-303规范,Bean Validation
一: JSR 303是JAVA EE 6中的一项子规范,叫做Bean Validation,官方参考实现是Hibernate Validator,此实现与Hibernate ORM没有任何关系.JSR ...
- spring 3.1 配置 JCR 303 Bean Validation
A) 导入Hibernate-Validator 要使用JSR303 校验框架, 需要加入框架的具体实现Hibernate-Validator, 在soureforge上下载最新的Hibernate ...
- JSR 303 - Bean Validation 介绍及最佳实践
JSR 303 - Bean Validation 介绍及最佳实践 JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案.2009 年 12 月 ...
- Bean Validation 技术规范特性概述
概述 Bean Validation 规范 Bean 是 Java Bean 的缩写.在 Java 分层架构的实际应用中,从表示层到持久化层.每一层都须要对 Java Bean 进行业务符合性验证,如 ...
- JSR303 Bean Validation 技术规范特性概述
概述 Bean Validation 规范 Bean 是 Java Bean 的缩写,在 Java 分层架构的实际应用中,从表示层到持久化层,每一层都需要对 Java Bean 进行业务符合性验证,如 ...
- Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC 配置校验器
Spring4新特性——泛型限定式依赖注入 Spring4新特性——核心容器的其他改进 Spring4新特性——Web开发的增强 Spring4新特性——集成Bean Validation 1.1(J ...
- Java参数验证Bean Validation 框架
1.为什么要做参数校验? 参数校验和业务逻辑代码分离,参数校验代码复用,统一参数校验方式.校验不太通过时统一异常描述. 2.bean validation规范 JSR303 规范(Bean Valid ...
随机推荐
- myeclipse6.6+maven跑springside4.1的demo
1.安装myeclipse6.6 2.myeclipse6.6安装maven 2.1 删除原有maven. 关闭Eclipse程序, 进入MyEclipse插件目录/eclipse/features ...
- SET FOREIGN_KEY_CHECKS=0;在Mysql中取消外键约束
Mysql中如果表和表之间建立的外键约束,则无法删除表及修改表结构. 解决方法是在Mysql中取消外键约束: SET FOREIGN_KEY_CHECKS=0; 然后将原来表的数据导 ...
- HDU 1465 2045 已知结果往前推
1465 不容易系列之一 Time Limit: 1000 MS Memory Limit: 32768 KB 64-bit integer IO format: %I64d , %I64u Java ...
- ios调用系统界面显示英文
调用系统相册界面 UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.sourceType ...
- 在线团队协作工具+在线UML工具
话不多说直接上https://worktile.com去看,顺便附上小众软件上面的介绍 默默增加worktile的外国原版https://trello.com/,worktile照着trello做的, ...
- Google guava cache源码解析1--构建缓存器(1)
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHas ...
- 关于.net core使用nginx做反向代理获取客户端ip的问题
1.正常情况下.net core获取客户端ip是比较简单的 /// <summary> /// 获取客户Ip /// </summary> /// <param name ...
- [ZJOI2010]基站选址(线段树优化dp)
坑待填. \(Code\ Below:\) #include <bits/stdc++.h> #define lson (rt<<1) #define rson (rt< ...
- Swift 里字符串(十一)OC 字符串和 Swift 字符串的转换
 to OC func _bridgeToObjectiveCImpl() -> AnyObject { if _guts.isSmall { return _guts.asSmall.wit ...
- salt-api return mysql返回的使用,记录操作日志
说在前面 折腾这个搞了半天,现做下记录 安装依赖(操作只在master端) yum install mysql-python or pip install mysql-python master端本地 ...