热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Validation(4)临时

使用Hibernate-Validator优雅的校验参数2019年01月01日13:17:31余生之君阅读数:337版权声明:本文为博主原创文章

使用Hibernate-Validator优雅的校验参数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/java_collect/article/details/85534054

 

文章目录

    • 何为Hibernate-Validator
      • 1. Hibernate-Validator 最基本的使用
      • 2. 常用的校验注解
      • 3. 自定义注解
      • 4. 分组验证

 

何为Hibernate-Validator

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

       如果项目的框架是spring boot的话,在spring-boot-starter-web 中已经包含了Hibernate-validator的依赖。Hibernate-Validator的主要使用的方式就是注解的形式,并且是“零配置”的,无需配置也可以使用。下面展示一个最简单的案例。

1. Hibernate-Validator 最基本的使用

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

@RestController@RequestMapping("/example")@Validatedpublic 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 "恭喜你拿到参数了";}}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. 在全局异常拦截中添加验证异常的处理

/*** 统一异常处理类*/
@RestControllerAdvice
public class GlobalExceptionHandler {&#64;ExceptionHandler(MethodArgumentNotValidException.class)&#64;ResponseStatus(HttpStatus.BAD_REQUEST)public BaseResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {StringBuilder errorInfo &#61; new StringBuilder();BindingResult bindingResult &#61; exception.getBindingResult();for(int i &#61; 0; i 0){errorInfo.append(",");}FieldError fieldError &#61; bindingResult.getFieldErrors().get(i);errorInfo.append(fieldError.getField()).append(" :").append(fieldError.getDefaultMessage());}//返回BaseResponseBaseResponse response &#61; new BaseResponse<>();response.setMsg(errorInfo.toString());response.setCode(DefaultErrorCode.error);return response;}&#64;ExceptionHandler(ConstraintViolationException.class)&#64;ResponseStatus(HttpStatus.BAD_REQUEST)public BaseResponse handleConstraintViolationException(ConstraintViolationException exception) {StringBuilder errorInfo &#61; new StringBuilder();String errorMessage ;Set> violations &#61; exception.getConstraintViolations();for (ConstraintViolation item : violations) {errorInfo.append(item.getMessage()).append(",");}errorMessage &#61; errorInfo.toString().substring(0, errorInfo.toString().length()-1);BaseResponse response &#61; new BaseResponse<>();response.setMsg(errorMessage);response.setCode(DefaultErrorCode.error);return response;}&#64;ExceptionHandler(Exception.class)&#64;ResponseStatus(HttpStatus.BAD_REQUEST)public BaseResponse handleDefaultException(Exception exception) {BaseResponse response &#61; new BaseResponse<>();response.setMsg("其他错误");response.setCode(DefaultErrorCode.error);return response;}
}

验证复杂参数的案例

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

&#64;Data
&#64;ApiModel("用户添加对象")
public class UserAddDto {&#64;NotBlank(message &#61; "用户id不能为空")&#64;ApiModelProperty(name &#61; "id", notes &#61; "用户id",example &#61; "2441634686")private String id;&#64;NotBlank(message &#61; "用户名称不能为空")&#64;Size(min &#61; 1,max &#61; 20,message &#61; "名称必须在1-20个字符内") //如果只有&#64;Size&#xff0c;name可为空&#64;ApiModelProperty(name &#61; "name", notes &#61; "用户名称",example &#61; "张飞")private String name;&#64;NotNull(message &#61; "开始时间不能为空")&#64;ApiModelProperty(name &#61; "startDate", notes &#61; "开始时间",example &#61; "2018-08-08")private String startDate;&#64;Size(max &#61; 12,message &#61; "不能超过12个")&#64;ApiModelProperty(name &#61; "list", notes &#61; "包含的方向列表")&#64;Valid //级联校验private List list;&#64;EnumCheck(enumClass &#61; SexEnum.class,message &#61; "用户类型不合法")&#64;ApiModelProperty(name &#61; "id", notes &#61; "用户类型",example &#61; "2")private Integer type;}

&#64;Data

&#64;ApiModel("方向对象")
public class DirectionDto {&#64;NotBlank(message &#61; "方向id不能为空")&#64;ApiModelProperty(name &#61; "list", notes &#61; "方向唯一标识",example &#61; "45587845465")private String id;&#64;NotBlank(message &#61; "开始日期不能为空")&#64;ApiModelProperty(name &#61; "startDate", notes &#61; "开始时间",example &#61; "2019-01-08")&#64;Length(max &#61; 8,message &#61; "开始日期不合法")private String startDate;&#64;ApiModelProperty(name &#61; "endDate", notes &#61; "结束时间",example &#61; "2019-01-14")private String endDate;
}

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

&#64;PostMapping(value &#61; "/add")&#64;ApiOperation(value &#61; "添加用户",notes &#61; "添加用户,方向可以为空")public BaseResponse add(&#64;Validated &#64;RequestBody UserAddDto addDto) {BaseResponse response &#61; new BaseResponse<>();response.setMsg("添加成功");return response;}

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

2. 常用的校验注解

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

  • 常用的
注解使用
&#64;NotNull被注释的元素&#xff08;任何元素&#xff09;必须不为 null, 集合为空也是可以的。没啥实际意义
&#64;NotEmpty用来校验字符串、集合、map、数组不能为null或空 
&#xff08;字符串传入空格也不可以&#xff09;&#xff08;集合需至少包含一个元素&#xff09;
&#64;NotBlank只用来校验字符串不能为null&#xff0c;空格也是被允许的 。校验字符串推荐使用&#64;NotEmpty
- 
&#64;Size(max&#61;, min&#61;)指定的字符串、集合、map、数组长度必须在指定的max和min内 
允许元素为null&#xff0c;字符串允许为空格
&#64;Length(min&#61;,max&#61;)只用来校验字符串&#xff0c;长度必须在指定的max和min内 允许元素为null
&#64;Range(min&#61;,max&#61;)用来校验数字或字符串的大小必须在指定的min和max内
字符串会转成数字进行比较&#xff0c;如果不是数字校验不通过
允许元素为null
&#64;Min()校验数字&#xff08;包括integer short long int 等&#xff09;的最小值&#xff0c;不支持小数即double和float
允许元素为null
&#64;Max()校验数字&#xff08;包括integer short long int 等&#xff09;的最小值&#xff0c;不支持小数即double和float
允许元素为null
- 
&#64;Pattern()正则表达式匹配&#xff0c;可用来校验年月日格式&#xff0c;是否包含特殊字符&#xff08;regexp &#61; "^[a-zA-Z0-9\u4e00-\u9fa5

除了&#64;Empty要求字符串不能全是空格&#xff0c;其他的字符串校验都是允许空格的。
message是可以引用常量的&#xff0c;但是如&#64;Size里max不允许引用对象常量&#xff0c;基本类型常量是可以的。
注意大部分规则校验都是允许参数为null&#xff0c;即当不存在这个值时&#xff0c;就不进行校验了

  • 不常用的

&#64;Null 被注释的元素必须为 null
&#64;AssertTrue 被注释的元素必须为 true
&#64;AssertFalse 被注释的元素必须为 false
&#64;DecimalMin(value) 被注释的元素必须是一个数字&#xff0c;其值必须大于等于指定的最小值
&#64;DecimalMax(value) 被注释的元素必须是一个数字&#xff0c;其值必须小于等于指定的最大值
&#64;Digits (integer, fraction) 被注释的元素必须是一个数字&#xff0c;其值必须在可接受的范围内
&#64;Past 被注释的元素必须是一个过去的日期
&#64;Future 被注释的元素必须是一个将来的日期
&#64;Email 被注释的元素必须是电子邮箱地址

3. 自定义注解

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

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

&#64;Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})&#64;Retention(RetentionPolicy.RUNTIME)&#64;Constraint(validatedBy &#61; EnumCheckValidator.class)public &#64;interface EnumCheck {/*** 是否必填 默认是必填的* &#64;return*/boolean required() default true;/*** 验证失败的消息* &#64;return*/String message() default "枚举的验证失败";/*** 分组的内容* &#64;return*/Class[] groups() default {};/*** 错误验证的级别* &#64;return*/Class[] payload() default {};/*** 枚举的Class* &#64;return*/Class> enumClass();/*** 枚举中的验证方法* &#64;return*/String enumMethod() default "validation";}

注解的业务逻辑实现类


public class EnumCheckValidator implements ConstraintValidator {private EnumCheck enumCheck;&#64;Overridepublic void initialize(EnumCheck enumCheck) {this.enumCheck &#61;enumCheck;}&#64;Overridepublic boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {// 注解表明为必选项 则不允许为空&#xff0c;否则可以为空if (value &#61;&#61; null) {return !this.enumCheck.required();}//最终的返回结果Boolean result&#61;Boolean.FALSE;// 获取 参数的数据类型Class valueClass &#61; value.getClass();try {Method method &#61; this.enumCheck.enumClass().getMethod(this.enumCheck.enumMethod(), valueClass);result &#61; (Boolean)method.invoke(null, value);if(result &#61;&#61; null){result &#61; Boolean.FALSE;}//所有异常需要在开发测试阶段发现完毕} 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 &#61; label;}public Integer getValue() {return value;}public void setValue(Integer value) {this.value &#61; value;}Sex(String label, int value) {this.label &#61; label;this.value &#61; value;}/*** 判断值是否满足枚举中的value* &#64;param value* &#64;return*/public static boolean validation(Integer value){for(Sex s:Sex.values()){if(Objects.equals(s.getValue(),value)){return true;}}return false;}
}

 

使用方式

&#64;EnumCheck(message &#61; "只能选男&#xff1a;1或女:2",enumClass &#61; Sex.class)private Integer sex;

 

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

4. 分组验证

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

添加一个名为ValidGroupA的接口&#xff08;接口内容可以是空的&#xff0c;所以就不列举代码&#xff09;
添加一个需要分组校验的字段

&#64;Data
public class ExampleVo {&#64;NotNull(message &#61; "主键不允许为空",groups &#61; ValidGroupA.class)private Integer id;&#64;NotBlank(message &#61; "用户名不能为空",groups &#61; Default.class)private String userName;&#64;Range(min &#61; 18,max &#61; 60,message &#61; "只能填报年龄在18~60岁的",groups &#61; Default.class)private String age;&#64;EnumCheck(message &#61; "只能选男&#xff1a;1或女:2",enumClass &#61; Sex.class,groups &#61; Default.class)private Integer sex;
}

改动接口的内容

&#64;RequestMapping(value &#61; "/info1",method &#61; RequestMethod.POST)public String test1(&#64;Validated({ValidGroupA.class,Default.class}) &#64;RequestBody ExampleVo vo){return "恭喜你拿到参数了";}

       这里我们可以注意一下&#xff0c;校验的注解由 &#64;Valid 改成了 &#64;Validated。进行测试&#xff0c;保留ValidGroupA.class和去掉ValidGroupA.class的测试。

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

部分引用及参考的文章
https://www.cnblogs.com/mr-yang-localhost/p/7812038.html#_label3_3


转:https://www.cnblogs.com/longxok/p/10912872.html



推荐阅读
author-avatar
金berends_941
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有