Spring Boot中结合Hibernate Validator可以实现优雅的参数校验,而不必在业务代码中写一大堆的参数校验逻辑。这里介绍一种结合全局异常捕获的方式来实现低耦合简洁的参数校验解决方案。
下面从两部分来介绍:
- 方法参数校验
- 使用实体传参
方法参数校验
新建一个Spring Boot工程,引入spring-boot-starter-web和commons-lang3依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
spring-boot-starter-web已经包含了hibernate-validator,所以无需单独引入
创建TestController 进行测试:
@RestController
@Validated
public class TestController {
@GetMapping("/hello")
public String hello(@NotBlank(message = "{required}") String name,
@Email(message = "{invalid}") String email) {
return "Hello World";
}
}
test1方法的name参数使用@NotBlank标注,表示不能为空,提示信息为{required}占位符里的内容;email参数使用@Email注解标注,表示必须为一个合法的邮箱值(可以为空),提示信息为{invalid}占位符里的内容。要让参数校验生效,我们还需在类上使用@Validated注解标注。
接下来定义上面两个占位符的内容。在resources目录下新建ValidationMessages.properties文件,内容如下:
required=\u4e0d\u80fd\u4e3a\u7a7a
invalid=\u683c\u5f0f\u4e0d\u5408\u6cd5
内容为中文转Unicode后的值,\u4e0d\u80fd\u4e3a\u7a7a
转为中文为“不能为空”,\u683c\u5f0f\u4e0d\u5408\u6cd5
转为中文为“格式不合法”。
启动项目,进行测试,带参数访问:
http://127.0.0.1:9090/hello?name=&&email=123
页面返回如下:
控制台异常如下:
javax.validation.ConstraintViolationException: hello.name: 不能为空, hello.email: 格式不合法
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:116) ~[spring-context-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
`````
可见,使用这种方式参数校验不通过时,会抛出javax.validation.ConstraintViolationException,我们使用全局异常捕获来处理这种异常:
创建GlobalExceptionHandler类:
@RestControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
/**
* 统一处理请求参数校验.
*
* @param e
* @return
*/
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleConstrainViolationException(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。重启项目,再次访问刚刚的链接,响应如下所示:
image.png
使用实体传参
当参数较少的时候可以使用上面这种方式,但如果参数众多上面的方式就略显繁琐了。这时候我们可以使用实体对象来进行传参。
为了模拟这种情况,新建User类:
@Data
public class User implements Serializable {
private static final long serialVersionUID = 6523421519574577128L;
@NotBlank(message = "{required}")
private String name;
@Email(message = "{invalid}")
private String email;
}
接着在TestController中创建一个hello2的接口:
@GetMapping("/hello2")
public String hello2(@Valid User user) {
return "Hello World2";
}
使用实体对象传参的方式参数校验需要在相应的参数前加上@Valid注解。重启项目,再次访问下面这个请求:
http://127.0.0.1:9090/hello2?name=&&email=123
页面如下:
控制台输出如下:
2019-08-14 18:31:51.327 WARN 18488 --- [nio-9090-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : 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;@341da5a9,.*]; default message [格式不合法]]
这时候我们需要在GlobalExceptionHandler捕获org.springframework.validation.BindException异常:
在GlobalExceptionHandler类中添加:
/**
* 统一请求参数校验(实体对象传参)
*
* @param e
* @return
*/
@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();
}
重启项目,再次访问刚刚的请求,响应如下所示:
image.png
我们把参数修改为合法参数:
http://127.0.0.1:9090/hello2?name=lconcise&&email=123@163.com
页面返回如下:
源码链接:https://github.com/lbshold/springboot/tree/master/Spring-Boot-Hibernate-Validator
参考文章:https://mrbird.cc/Spring-Boot-Hibernate-Validator-Params-Check.html
网友评论