以下内容转载自: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. linux 下载文件

    工作流程 1.tar pczvf file.tar.gz file 2.sz file.tar.gz:下载. 3.rm -i file.tar.gz: 删除.

  2. Strom开发配置手册

    一:Storm集群搭建 1.本次开发使用的是storm0.9.3 2.Storm0.9.3集群搭建: 1)storm集群角色包含集群主节点Nimbus:集群从节点Supervisor 2)集群安装:先 ...

  3. 2.虚拟机安装的ubuntu全屏显示

    虚拟机下面安装了ubuntu系统,显示的屏幕只有那么一小块儿,不知道如何才能全屏,那么如何全屏呢?且看下面经验. 方法/步骤 打开虚拟机,并点击要更改成全屏的那个ubuntu系统的电源 我的虚拟机名字 ...

  4. 利用Delphi编程控制摄像头(图)

    你的电脑有没有摄像头?看到别人用QQ玩视屏你会不会去想怎么实现的?这里介绍使用DELPHI使用MS的 AVICAP32.DLL就可轻松的实现对摄像头编程,如果再加上你的网络编程水平,实现一个视屏聊天就 ...

  5. uniGUI for C++ builder下如何利用FastReport实现数据记录本地打印

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/dlboy2018/article/details/81040260 (中行雷威2018.7.14于杭 ...

  6. Linux-系统相关命令及配置文件

    1.查看/配置主机名 # 查看主机名 hostname # 配置主机名(临时) hostname <HOSTNAME> # 配置主机名(永久) hostnamectl set-hostna ...

  7. AlexNet详解2

    此处以caffe官方提供的AlexNet为例. 目录: 1.背景 2.框架介绍 3.步骤详细说明 5.参考文献 背景: AlexNet是在2012年被发表的一个金典之作,并在当年取得了ImageNet ...

  8. 一步步改造wcf,数据加密传输-匿名客户端加密传输

    一步步改造wcf,数据加密传输-匿名客户端加密传输 百度搜索wcf加密传输,资料挺多,真真正正能用的确不多. 一是本来就很复杂,而是各位大神给的资料不足.本人今天来提供一个简易方法. 匿名客户端加密传 ...

  9. 设计模式:Builder

    简介 建造者模式(Builder),将一个复杂对象的表示和它的构建分离,这样同样的构造过程可以创建出不同的对象状态. 类图 下面的Product是要创建的对象的目标类型,产品. Builder 创建一 ...

  10. “全栈2019”Java多线程第三十三章:await与signal/signalAll

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...