Spring Boot中结合Hibernate Validator可以实现优雅的参数校验,而不必在业务代码中写一大堆的参数校验逻辑。Hibernate Validator的基本使用可以参考Spring表单校验,这里介绍一种结合全局异常捕获的方式来实现低耦合简洁的参数校验解决方案。 

方法参数校验

新建一个Spring Boot工程,版本为2.1.0.RELEASE,artifactId为validator,并引入spring-boot-starter-web和commons-lang3依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
</dependencies>

 项目结构如下所示:

spring boot Validator 时间 validate springboot_spring

spring-boot-starter-web已经包含了hibernate-validator,所以无需单独引入:

spring boot Validator 时间 validate springboot_Email_02

在com.example.demo下新建controller包,然后创建TestController,定义一个test1方法:

@RestController
@Validated
public class TestController {

    @GetMapping("test1")
    public String test1(
            @NotBlank(message = "{required}") String name,
            @Email(message = "{invalid}") String email) {
        return "success";
    }
}

 test1方法的name参数使用@NotBlank标注,表示不能为空,提示信息为{required}占位符里的内容;email参数使用@Email注解标注,表示必须为一个合法的邮箱值(可以为空),提示信息为{invalid}占位符里的内容。要让参数校验生效,我们还需在类上使用@Validated注解标注。

接下来定义上面两个占位符的内容。在resources目录下新建ValidationMessages.properties文件,内容如下:

required=\u4e0d\u80fd\u4e3a\u7a7a
invalid=\u683c\u5f0f\u4e0d\u5408\u6cd5

 内容为中文转Unicode后的值,可以使用http://tool.chinaz.com/tools/unicode.aspx网站转换,\u4e0d\u80fd\u4e3a\u7a7a转为中文为“不能为空”,\u683c\u5f0f\u4e0d\u5408\u6cd5转为中文为“格式不合法”。

启动项目,使用Postman进行测试,参数内容如下所示:

spring boot Validator 时间 validate springboot_Email_03

这里name参数值为空,email参数值为123,访问后,控制台输出异常如下:

spring boot Validator 时间 validate springboot_Email_04

可见,使用这种方式参数校验不通过时,会抛出javax.validation.ConstraintViolationException,我们使用全局异常捕获来处理这种异常:

在com.example.demo下新建handler包,然后创建GlobalExceptionHandler:

@RestControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {

    /**
     * 统一处理请求参数校验(普通传参)
     *
     * @param e ConstraintViolationException
     * @return FebsResponse
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleConstraintViolationException(ConstraintViolationException e) {
        StringBuilder message = new StringBuilder();
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        for (ConstraintViolation<?> violation : violations) {
            Path path = violation.getPropertyPath();
            String[] pathArr = StringUtils.splitByWholeSeparatorPreserveAllTokens(path.toString(), ".");
            message.append(pathArr[1]).append(violation.getMessage()).append(",");
        }
        message = new StringBuilder(message.substring(0, message.length() - 1));
        return message.toString();
    }
}

 上面主要的逻辑是获取校验不通过的参数名称,然后拼接上提示信息,并且HTTP返回状态码为400。重启项目,再次访问刚刚的链接,响应如下所示:

spring boot Validator 时间 validate springboot_java_05

使用实体传参

当参数较少的时候可以使用上面这种方式,但如果参数众多上面的方式就略显繁琐了。这时候我们可以使用实体对象来进行传参。

为了模拟这种情况,我们在com.example.demo路径下新建domain包,然后新建User类:

public class User implements Serializable {
    private static final long serialVersionUID = -2731598327208972274L;

    @NotBlank(message = "{required}")
    private String name;

    @Email(message = "{invalid}")
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

 接着在TestController里创建一个test2方法:

@GetMapping("test2")
public String test2(@Valid User user) {
    return "success";
}

 使用实体对象传参的方式参数校验需要在相应的参数前加上@Valid注解。重启项目,再次访问下面这个请求:

spring boot Validator 时间 validate springboot_Email_03

控制台会输出如下信息:

Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 2 errors
Field error in object 'user' on field 'name': rejected value []; codes [NotBlank.user.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [不能为空]
Field error in object 'user' on field 'email': rejected value [123]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@5fb82092,org.springframework.validation.beanvalidation.SpringValidatorAdapter$ResolvableAttribute@cc0c307]; default message [格式不合法]]

这时候我们需要在GlobalExceptionHandler捕获org.springframework.validation.BindException异常:

/**
 * 统一处理请求参数校验(实体对象传参)
 *
 * @param e BindException
 * @return FebsResponse
 */
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String validExceptionHandler(BindException e) {
    StringBuilder message = new StringBuilder();
    List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
    for (FieldError error : fieldErrors) {
        message.append(error.getField()).append(error.getDefaultMessage()).append(",");
    }
    message = new StringBuilder(message.substring(0, message.length() - 1));
    return message.toString();

}

 重启项目,再次访问刚刚的请求,响应如下所示:

spring boot Validator 时间 validate springboot_java_07

我们将请求参数改为合法的内容:

spring boot Validator 时间 validate springboot_Email_08

点击访问,响应如下所示:

spring boot Validator 时间 validate springboot_spring_09