以下内容转载自: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规范的更多相关文章

  1. Spring3.1 对Bean Validation规范的新支持(方法级别验证)

    上接Spring提供的BeanPostProcessor的扩展点-1继续学习. 一.Bean Validation框架简介 写道Bean Validation standardizes constra ...

  2. 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 ...

  3. JSR-303规范,Bean Validation

    一: JSR 303是JAVA EE 6中的一项子规范,叫做Bean Validation,官方参考实现是Hibernate Validator,此实现与Hibernate ORM没有任何关系.JSR ...

  4. spring 3.1 配置 JCR 303 Bean Validation

    A) 导入Hibernate-Validator  要使用JSR303 校验框架, 需要加入框架的具体实现Hibernate-Validator, 在soureforge上下载最新的Hibernate ...

  5. JSR 303 - Bean Validation 介绍及最佳实践

    JSR 303 - Bean Validation 介绍及最佳实践 JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案.2009 年 12 月 ...

  6. Bean Validation 技术规范特性概述

    概述 Bean Validation 规范 Bean 是 Java Bean 的缩写.在 Java 分层架构的实际应用中,从表示层到持久化层.每一层都须要对 Java Bean 进行业务符合性验证,如 ...

  7. JSR303 Bean Validation 技术规范特性概述

    概述 Bean Validation 规范 Bean 是 Java Bean 的缩写,在 Java 分层架构的实际应用中,从表示层到持久化层,每一层都需要对 Java Bean 进行业务符合性验证,如 ...

  8. Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC 配置校验器

    Spring4新特性——泛型限定式依赖注入 Spring4新特性——核心容器的其他改进 Spring4新特性——Web开发的增强 Spring4新特性——集成Bean Validation 1.1(J ...

  9. Java参数验证Bean Validation 框架

    1.为什么要做参数校验? 参数校验和业务逻辑代码分离,参数校验代码复用,统一参数校验方式.校验不太通过时统一异常描述. 2.bean validation规范 JSR303 规范(Bean Valid ...

随机推荐

  1. myeclipse6.6+maven跑springside4.1的demo

    1.安装myeclipse6.6 2.myeclipse6.6安装maven 2.1 删除原有maven. 关闭Eclipse程序, 进入MyEclipse插件目录/eclipse/features ...

  2. SET FOREIGN_KEY_CHECKS=0;在Mysql中取消外键约束

      Mysql中如果表和表之间建立的外键约束,则无法删除表及修改表结构.   解决方法是在Mysql中取消外键约束:  SET FOREIGN_KEY_CHECKS=0;     然后将原来表的数据导 ...

  3. HDU 1465 2045 已知结果往前推

    1465 不容易系列之一 Time Limit: 1000 MS Memory Limit: 32768 KB 64-bit integer IO format: %I64d , %I64u Java ...

  4. ios调用系统界面显示英文

    调用系统相册界面 UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.sourceType ...

  5. 在线团队协作工具+在线UML工具

    话不多说直接上https://worktile.com去看,顺便附上小众软件上面的介绍 默默增加worktile的外国原版https://trello.com/,worktile照着trello做的, ...

  6. Google guava cache源码解析1--构建缓存器(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHas ...

  7. 关于.net core使用nginx做反向代理获取客户端ip的问题

    1.正常情况下.net core获取客户端ip是比较简单的 /// <summary> /// 获取客户Ip /// </summary> /// <param name ...

  8. [ZJOI2010]基站选址(线段树优化dp)

    坑待填. \(Code\ Below:\) #include <bits/stdc++.h> #define lson (rt<<1) #define rson (rt< ...

  9. Swift 里字符串(十一)OC 字符串和 Swift 字符串的转换

     to OC func _bridgeToObjectiveCImpl() -> AnyObject { if _guts.isSmall { return _guts.asSmall.wit ...

  10. salt-api return mysql返回的使用,记录操作日志

    说在前面 折腾这个搞了半天,现做下记录 安装依赖(操作只在master端) yum install mysql-python or pip install mysql-python master端本地 ...