何为Hibernate-Validator

在RESTful Web Services的接口服务中,会有各种各样的入参,我们不可能完全不做任何校验就直接进入到业务处理的环节,通常我们会有一个基础的数据验证的机制,待这些验证过程完毕,结果无误后,参数才会进入到正式的业务处理中。而数据验证又分为两种,一种是无业务关联的规则性验证,一种是根据现有数据进行的联动性数据验证(简单来说,参数的合理性,需要查数据库)。而Hibernate-Validator则适合做无业务关联的规则性验证。

Hibernate-Validator的相关依赖

如果项目的框架是spring boot的话,在spring-boot-starter-web 中已经包含了Hibernate-validator的依赖,我们点开spring-boot-starter-web的pom.xml则可以看到相关的依赖内容。

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starters</artifactId><version>1.5.10.RELEASE</version></parent><artifactId>spring-boot-starter-web</artifactId><name>Spring Boot Web Starter</name><description>Starter for building web, including RESTful, applications using SpringMVC. Uses Tomcat as the default embedded container</description><url>http://projects.spring.io/spring-boot/</url><organization><name>Pivotal Software, Inc.</name><url>http://www.spring.io</url></organization><properties><main.basedir>${basedir}/../..</main.basedir></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></dependency><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId></dependency></dependencies>

如果是其他的框架风格的话,引入如下的依赖就可以了。

<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>6.0.10.Final</version>
</dependency>

初步使用Hibernate-Validator

以下代码环境均在Spring boot 1.5.10的版本下运行。

Hibernate-Validator的主要使用的方式就是注解的形式,并且是“零配置”的,无需配置也可以使用。下面用一个最简单的案例。

  • Hibernate-Validator 最基本的使用

    1.添加一个普通的接口信息,参数是@RequestParam类型的,传入的参数是id,且id不能小于10。

@RestController
@RequestMapping("/example")
@Validated
public class ExampleController{/***  用于测试* @param id id数不能小于10 @RequestParam类型的参数需要在Controller上增加@Validated* @return*/@RequestMapping(value = "/info",method = RequestMethod.GET)public String test(@Min(value = 10, message = "id最小只能是10") @RequestParam("id")Integer id){return "恭喜你拿到参数了";}
}

2.在全局异常拦截中添加验证异常的处理

@Slf4j
@ControllerAdvice
@Component
public class GlobalExceptionHandler{@ExceptionHandler@ResponseBody@ResponseStatus(HttpStatus.BAD_REQUEST)public String handle(ConstraintViolationException exception, HttpServletRequest request){Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();StringBuffer errorInfo = new StringBuffer();for (ConstraintViolation<?> item : violations) {/**打印验证不通过的信息*/errorInfo.append(item.getMessage());errorInfo.append(",");}log.error("{}接口参数验证失败,内容如下:{}",request.getRequestURI(),errorInfo.toString());return "您的请求失败,参数验证失败,失败信息如下:"+ errorInfo.toString();}
}

3.一个简单的测试。

  • 验证复杂参数的案例

    1.添加一个vo的实体信息。

/*** 用户的vo类* @author dengyun*/
@Data
public class ExampleVo{@NotBlank(message = "用户名不能为空")private String userName;@Range(min = 18,max = 60,message = "只能填报年龄在18~60岁的")private String age;
}

2.添加一个POST请求的接口。

    /*** 用于测试* @param vo 按照vo的验证* @return*/@RequestMapping(value = "/info1",method = RequestMethod.POST)public String test1(@Valid  @RequestBody ExampleVo vo){return "恭喜你拿到参数了";}

3.在全局异常拦截中添加验证处理的结果

    @ResponseBody@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(MethodArgumentNotValidException.class)public String handle(MethodArgumentNotValidException exception,HttpServletRequest request){StringBuffer errorInfo=new StringBuffer();List<ObjectError> errors = exception.getBindingResult().getAllErrors();for(int i=0;i<errors.size();i++){errorInfo.append(errors.get(i).getDefaultMessage()+",");}log.error("{},接口参数验证失败:{}",request,errorInfo.toString());return "您的请求失败,参数验证失败,失败信息如下:"+errorInfo.toString();}

4.一个简单的测试

我个人比较推荐使用全局异常拦截处理的方式去处理Hibernate-Validator的验证失败后的处理流程,这样能能减少Controller层或Services层的代码逻辑处理。虽然它也能在Controller中增加BindingResult的实例来获取数据,但是并不推荐。

更加灵活的运用

首先列举一下Hibernate-Validator所有的内置验证注解。

@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 附加的 constraint
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内

这些注解能适应我们绝大多数的验证场景,但是为了应对更多的可能性,我们需要增加注解功能配合Hibernate-Validator的其他的特性,来满足验证的需求。

1. 自定义注解

  • 添加自定义注解

我们一定会用到这么一个业务场景,vo中的属性必须符合枚举类中的枚举。Hibernate-Validator中还没有关于枚举的验证规则,那么,我们则需要自定义一个枚举的验证注解。

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumCheckValidator.class)
public @interface EnumCheck {/*** 是否必填 默认是必填的* @return*/boolean required() default true;/*** 验证失败的消息* @return*/String message() default "枚举的验证失败";/*** 分组的内容* @return*/Class<?>[] groups() default {};/*** 错误验证的级别* @return*/Class<? extends Payload>[] payload() default {};/*** 枚举的Class* @return*/Class<? extends Enum<?>> enumClass();/*** 枚举中的验证方法* @return*/String enumMethod() default "validation";
}
  • 注解的业务逻辑实现类
public class EnumCheckValidator implements ConstraintValidator<EnumCheck,Object>{private EnumCheck enumCheck;@Overridepublic void initialize(EnumCheck enumCheck){this.enumCheck =enumCheck;}@Overridepublic boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext){// 注解表明为必选项 则不允许为空,否则可以为空if (value == null) {return this.enumCheck.required()?false:true;}//最终的返回结果Boolean result=Boolean.FALSE;// 获取 参数的数据类型Class<?> valueClass = value.getClass();try {Method method = this.enumCheck.enumClass().getMethod(this.enumCheck.enumMethod(), valueClass);result = (Boolean)method.invoke(null, value);result= result == null ? false : result;//所有异常需要在开发测试阶段发现完毕} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}finally {return result;}}
}
  • 编写枚举类
public enum  Sex{MAN("男",1),WOMAN("女",2);private String label;private Integer value;public String getLabel(){return label;}public void setLabel(String label){this.label = label;}public Integer getValue(){return value;}public void setValue(Integer value){this.value = value;}Sex(String label, int value) {this.label = label;this.value = value;}/*** 判断值是否满足枚举中的value* @param value* @return*/public static boolean validation(Integer value){for(Sex s:Sex.values()){if(Objects.equals(s.getValue(),value)){return true;}}return false;}
}
  • 使用方式
    @EnumCheck(message = "只能选男:1或女:2",enumClass = Sex.class)private Integer sex;
  • 一个简单的测试

我们甚至可以在自定义注解中做更加灵活的处理,甚至把与数据库的数据校验的也写成自定义注解,来进行数据验证的调用。

2. Hibernate-Validator的分组验证

同一个校验规则,不可能适用于所有的业务场景,对此,对每一个业务场景去编写一个校验规则,又显得特别冗余。这里我们刚好可以用到Hibernate-Validator的分组功能。

  • 添加一个名为ValidGroupA的接口(接口内容可以是空的,所以就不列举代码)
  • 添加一个需要分组校验的字段
@Data
public class ExampleVo{@NotNull(message = "主键不允许为空",groups = ValidGroupA.class)private Integer id;@NotBlank(message = "用户名不能为空",groups = Default.class)private String userName;@Range(min = 18,max = 60,message = "只能填报年龄在18~60岁的",groups = Default.class)private String age;@EnumCheck(message = "只能选男:1或女:2",enumClass = Sex.class,groups = Default.class)private Integer sex;
}
  • 改动接口的内容
    @RequestMapping(value = "/info1",method = RequestMethod.POST)public String test1(@Validated({ValidGroupA.class,Default.class})  @RequestBody ExampleVo vo){return "恭喜你拿到参数了";}

这里我们可以注意一下,校验的注解由 @Valid 改成了 @Validated

  • 进行测试,保留ValidGroupA.class和去掉ValidGroupA.class的测试。

    • 保留ValidGroupA.class

    • 去掉ValidGroupA.class

使用分组能极大的复用需要验证的类信息。而不是按业务重复编写冗余的类。然而Hibernate-Validator还提供组序列的形式进行顺序式校验,此处就不重复列举了。我认为顺序化的校验,场景更多的是在业务处理类,例如联动的属性验证,值的有效性很大程度上不能从代码的枚举或常量类中来校验。

部分引用及参考的文章

  • springboot使用hibernate- validator校验

使用Hibernate-Validator优雅的验证RESTful Web Services的参数相关推荐

  1. Jboss RestEasy构建简单的RESTful Web Services示例(1)

    2019独角兽企业重金招聘Python工程师标准>>> 项目上要用到webservice,鉴于现在restful webservice比较流行,打算用restful来建立webser ...

  2. RESTful Web Services初探

    RESTful Web Services初探 作者:杜刚 近几年,RESTful Web Services渐渐开始流行,大量用于解决异构系统间的通信问题.很多网站和应用提供的API,都是基于RESTf ...

  3. JAX-RS(Java API for RESTful Web Services)常用注解

    为什么80%的码农都做不了架构师?>>>    概述 JAX-RS(Java API for RESTful Web Services)是Java 提供用于开发RESTful Web ...

  4. RESTful Web Services in Spring 3(下)转载

    上一篇我主要发了RESTful Web Services in Spring 3的服务端代码,这里我准备写客户端的代码. 上篇得连接地址为:http://yangjizhong.iteye.com/b ...

  5. cxf开发Restful Web Services

    一.restful web services rest全称是Representation State Transfer(表述性状态转移).它是一种软件架构风格,只是提供了一组设计原则和约束条件.在re ...

  6. jboss7 Java API for RESTful Web Services (JAX-RS) 官方文档

    原文:https://docs.jboss.org/author/display/AS7/Java+API+for+RESTful+Web+Services+(JAX-RS) Content Tuto ...

  7. java官方 jax rs_jboss7 Java API for RESTful Web Services (JAX-RS) 官方文档

    原文:https://docs.jboss.org/author/display/AS7/Java+API+for+RESTful+Web+Services+(JAX-RS) Content Tuto ...

  8. Spring MVC 4 RESTFul Web Services CRUD例子(带源码)【这才是restful,超经典】

    [本系列其他教程正在陆续翻译中,点击分类:spring 4 mvc 进行查看.源码下载地址在文章末尾.] [翻译 by 明明如月 QQ 605283073] 原文地址:http://websystiq ...

  9. 《RESTful Web Services》第一章 使用统一接口

    序言 HTTP是一种应用层协议.SOAP和一些Ajax Web框架都将HTTP作为一种传输信息的协议,难以充分利用HTTP层的基础设施. 1.2 如何保持交互的可见性 可见性是HTTP的一个核心特征. ...

最新文章

  1. 经典大数据面试题及解析
  2. Linux qfile中文文件名,QFile无法打开包含unicode字符的文件名
  3. golang database/sql包 简介
  4. instancetype和id的区别
  5. server多笔记录拼接字符串 sql_Java拼接“1亿行字符串”你会遇到什么问题?
  6. ACM-ICPC 2018 徐州赛区网络预赛 Morgana Net
  7. hms能适配鸿蒙吗,国产手机即将抱团?魅族率先使用HMS服务,或多家国产适配鸿蒙!...
  8. 陕西专科学校王牌计算机专业,陕西省高职专科院校排名+王牌专业
  9. C#多线程编程系列(五)- C# ConcurrentBag的实现原理
  10. 04_SSM整合ActiveMQ支持多种类型消息
  11. 设计灵感|如何做好网页后台数据展示的设计?
  12. C# datetime 格式化
  13. BZOJ 3747 POI2015 Kinoman 段树
  14. ffmpeg常用数据结构4
  15. prune研究记录(二)
  16. SEO知识分享一,选择关键词
  17. localStorage的跨与实现方案
  18. (转)“跑批”发展编年史
  19. Java中parseInt()和valueOf(),toString()的区别
  20. c语言中乘法和除法哪个优先,C语言运算符和优先级

热门文章

  1. MySQL Performance-Schema(一) 配置篇
  2. 'SVN更新' has encountered a problem :An internal error occurred during: svn错误
  3. android进程调试(ro.debuggable=1或android:debuggable=true)----JDWP线程
  4. 应急模拟系统功能结构图
  5. Quartz执行周期配置
  6. 英语美句-每日积累-02
  7. Liunx的常用命令
  8. vmware centos 7 更新vmware-tools
  9. [zz]c++ list sort方法
  10. 简单程序计算无穷级数e^x