Spring Boot配合Hibernate Validator参数校验

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

方法参数校验

新建一个Spring Boot工程,版本为2.1.0.RELEASE,artifactIdvalidator,并引入spring-boot-starter-webcommons-lang3依赖:

1
2
3
4
5
6
7
8
9
10
11
 <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>

项目结构如下所示:

QQ截图20190227113219.png

spring-boot-starter-web已经包含了hibernate-validator,所以无需单独引入: QQ截图20190227114315.png

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

1
2
3
4
5
6
7
8
9
10
11
@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文件,内容如下:

1
2
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进行测试,参数内容如下所示:

QQ截图20190227140409.png

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

QQ截图20190227140521.png

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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。重启项目,再次访问刚刚的链接,响应如下所示:

QQ截图20190227141744.png

使用实体传参

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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方法:

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

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

控制台会输出如下信息:

1
2
3
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异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 统一处理请求参数校验(实体对象传参)
*
* @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();

}

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

QQ截图20190227143248.png

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

QQ截图20190227143400.png

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

QQ截图20190227143434.png

源码链接:https://github.com/wuyouzhuguli/SpringAll/tree/master/46.Spring-Boot-Hibernate-Validator

请作者喝瓶肥宅水🥤

0