使用Hibernate-Validator优雅的验证RESTful Web Services的参数

使用Hibernate-Validator优雅的验证RESTful Web Services的参数

何为Hibernate-Validator
在RESTful Web Services的接口服务中,会有各种各样的入参,我们不可能完全不做任何校验就直接进入到业务处理的环节,通常我们会有一个基础的数据验证的机制,待这些验证过程完毕,结果无误后,参数才会进入到正式的业务处理中。而数据验证又分为两种,一种是无业务关联的规则性验证,一种是根据现有数据进行的联动性数据验证(简单来说,参数的合理性,需要查数据库)。而Hibernate-Validator则适合做无业务关联的规则性验证。Hibernate-Validator的相关依赖
如果项目的框架是spring boot的话,在spring-boot-starter-web 中已经包含了Hibernate-validator的依赖,我们点开spring-boot-starter-web的pom.xml则可以看到相关的依赖内容。

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 Spring
        MVC. 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>

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

xml<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。

java@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.在全局异常拦截中添加验证异常的处理

java@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();
}
}

验证复杂参数的案例
1.添加一个vo的实体信息。

java/**
* 用户的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请求的接口。

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

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

java @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();
}

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

  • 或者自定义使用工具类: Validation工具类:

    public class ValidationUtils {

     /**
      * 使用hibernate的注解来进行验证
      */
     private static Validator validator = Validation
             .byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory().getValidator();
    
    
     /**
      * 功能描述:
    
      * 〈注解验证参数〉
      *
      * @param obj
      * @see [相关类/方法](可选)
      * @since [产品/模块版本](可选)
      */
     public static <T> void validate(T obj) {
         Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj);
         // 抛出检验异常
         if (constraintViolations.size() > 0) {
             throw new RuntimeException(String.format("参数校验失败:%s", constraintViolations.iterator().next().getMessage()));
         }
     }
    

    }

在代码中调用工具类进行参数校验:

ValidationUtils.validate(requestDto);更加灵活的运用

首先列举一下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中还没有关于枚举的验证规则,那么,我们则需要自定义一个枚举的验证注解。

java@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";
}

注解的业务逻辑实现类

javapublic class EnumCheckValidator implements ConstraintValidator<EnumCheck,Object> {
private EnumCheck enumCheck;

@Override
public void initialize(EnumCheck enumCheck) {
this.enumCheck =enumCheck;
}

@Override
public 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;
}
}
}

复制代码
*
编写枚举类

javapublic 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;
}
}

使用方式

java @EnumCheck(message = "只能选男:1或女:2",enumClass = Sex.class)
private Integer sex;

我们甚至可以在自定义注解中做更加灵活的处理,甚至把与数据库的数据校验的也写成自定义注解,来进行数据验证的调用。2. Hibernate-Validator的分组验证
同一个校验规则,不可能适用于所有的业务场景,对此,对每一个业务场景去编写一个校验规则,又显得特别冗余。这里我们刚好可以用到Hibernate-Validator的分组功能。
*
添加一个名为ValidGroupA的接口(接口内容可以是空的,所以就不列举代码)
*
添加一个需要分组校验的字段

java@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;
}

改动接口的内容

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

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

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

springboot使用hibernate- validator校验

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值