使用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则可以看到相关的依赖内容。

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

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

<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.一个简单的测试。

验证失败的案例.png

  • 验证复杂参数的案例

    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.一个简单的测试

复杂参数校验失败.png

我个人比较推荐使用全局异常拦截处理的方式去处理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;

    @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;
        }
    }
}
  • 编写枚举类
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
      保留分组的测试.png
    • 去掉ValidGroupA.class
      去掉分组后的测试.png

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

部分引用及参考的文章

目录
相关文章
|
30天前
|
网络协议 Serverless API
现代化 Web 应用构建问题之验证各个服务是否已成功部署如何解决
现代化 Web 应用构建问题之验证各个服务是否已成功部署如何解决
14 1
|
1月前
|
Java API 数据库
【神操作!】Spring Boot打造RESTful API:从零到英雄,只需这几步,让你的Web应用瞬间飞起来!
【8月更文挑战第12天】构建RESTful API是现代Web开发的关键技术之一。Spring Boot因其实现简便且功能强大而深受开发者喜爱。本文以在线图书管理系统为例,展示了如何利用Spring Boot快速构建RESTful API。从项目初始化、实体定义到业务逻辑处理和服务接口实现,一步步引导读者完成API的搭建。通过集成JPA进行数据库操作,以及使用控制器类暴露HTTP端点,最终实现了书籍信息的增删查改功能。此过程不仅高效直观,而且易于维护和扩展。
38 1
|
20天前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
56 0
|
20天前
|
开发者 Java 前端开发
Struts 2验证框架:如何让数据校验成为Web开发的隐形守护者?揭秘前后端一致性的秘诀
【8月更文挑战第31天】在现代Web开发中,数据验证对确保应用健壮性和良好用户体验至关重要。随着前后端分离架构的普及,保证数据校验一致性尤为关键。Struts 2 验证框架基于 JavaBean 验证 API(JSR 303/JSR 380),允许开发者通过注解或 XML 配置轻松定义验证规则,确保输入数据在执行业务逻辑前已通过验证。此外,Struts 2 支持与前端 JavaScript 验证相结合,确保前后端数据校验一致,提升用户体验。通过注解、XML 配置和资源文件,开发者可以轻松定义和调整验证规则,实现前后端一致的数据校验,提高应用健壮性。
28 0
|
20天前
|
Java 网络架构 数据格式
Struts 2 携手 RESTful:颠覆传统,重塑Web服务新纪元的史诗级组合!
【8月更文挑战第31天】《Struts 2 与 RESTful 设计:构建现代 Web 服务》介绍如何结合 Struts 2 框架与 RESTful 设计理念,构建高效、可扩展的 Web 服务。Struts 2 的 REST 插件提供简洁的 API 和约定,使开发者能快速创建符合 REST 规范的服务接口。通过在 `struts.xml` 中配置 `&lt;rest&gt;` 命名空间并使用注解如 `@Action`、`@GET` 等,可轻松定义服务路径及 HTTP 方法。
30 0
|
20天前
|
前端开发 API 开发者
JSF与RESTful服务的完美邂逅:如何打造符合现代Web潮流的数据交互新体验
【8月更文挑战第31天】随着互联网技术的发展,RESTful架构风格因其实现简便与无状态特性而在Web服务构建中日益流行。本文探讨如何结合JavaServer Faces (JSF) 和 JAX-RS 构建RESTful API,展示从前端到后端分离的完整解决方案。通过定义资源类、配置 `web.xml` 文件以及使用依赖注入等步骤,演示了在JSF项目中实现RESTful服务的具体过程,为Java开发者提供了实用指南。
29 0
|
20天前
|
安全 Java 云计算
JSF 应用究竟何去何从?云端部署能否成为其全新突破点?快来一探究竟!
【8月更文挑战第31天】本文介绍了将JavaServer Faces(JSF)应用部署到云平台的过程。首先,根据成本、功能、可靠性和安全性选择合适的云平台。接着,展示了构建简单JSF应用的示例代码。最后,以AWS Elastic Beanstalk为例,详细说明了部署流程。部署至云端可提升应用的可用性、扩展性和安全性。
30 0
|
20天前
|
安全 前端开发 PHP
构建与验证表单:传统PHP与Laravel框架的比较分析——探索Web开发中表单处理的优化策略和最佳实践
【8月更文挑战第31天】在 Web 开发中,表单构建与数据验证至关重要。传统 PHP 方法需手动处理 HTML 表单和数据验证,而 Laravel 框架则提供了一种更现代、高效的解决方案。本文通过对比传统 PHP 和 Laravel 的方法,探讨表单构建与验证的最佳实践。Laravel 通过简洁的语法糖、内置的数据过滤和验证机制,显著提升了代码的安全性和可维护性,适用于大型项目或需要快速开发的场景。然而,在追求灵活性的小型项目中,直接使用 PHP 仍是不错的选择。了解两者的优劣,有助于开发者根据项目需求做出最佳决策。
24 0
|
21天前
|
API 网络安全 数据库
Web2py框架如何颠覆传统的RESTful API开发?掌握这些技巧,让你的开发效率飞跃!
【8月更文挑战第31天】Web2py是一款全栈Python Web框架,适用于快速开发复杂交互的Web应用。本文将介绍如何使用Web2py创建RESTful API,包括设置新控制器、定义RESTful路由、处理数据库交互、确保API安全性、编写文档与使用Swagger、测试API以及部署时的注意事项。Web2py的高度抽象和易用性使其成为实现RESTful API的理想选择,帮助开发者专注于业务逻辑而非技术细节。
21 0
|
27天前
|
JSON Java API
【Azure Developer】如何验证 Azure AD的JWT Token (JSON Web 令牌)?
【Azure Developer】如何验证 Azure AD的JWT Token (JSON Web 令牌)?