博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
BindException、ConstraintViolationException、MethodArgumentNotValidException入参验证异常分析和全局异常处理解决方法
阅读量:3942 次
发布时间:2019-05-24

本文共 11394 字,大约阅读时间需要 37 分钟。

Spring validation验证框架注解

Spring validation验证框架提供了大量接口入参检验注解,注意三个非空注解:

  • @NotNull:验证对象是否不为null, 无法查检长度为0的字符串
  • @NotBlank:检查约束 (字符串) 是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格
  • @NotEmpty:检查(集合)约束元素是否为NULL或者是EMPTY
  • @Size(min=,max=):验证对象(Array,Collection,Map,String)长度是否在给定的范围之内。不要错用了异常类型,比如在int上不可用@size,而@Length(min=, max=) 只适用于String 类型
  • @AssertTrue: 验证 Boolean 对象是否为 true,@AssertFalse: 验证 Boolean 对象是否为 false
  • @Past: 验证 Date 和 Calendar 对象是否在当前时间之前 ,@Future: 验证 Date 和 Calendar对象是否在当前时间之后 ,@Pattern: 验证 String 对象是否符合正则表达式的规则
  • 建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为"“时无法转换为int,但可以转换为Stirng为”",Integer为null
    @Min: 验证 Number 和 String对象是否大等于指定的值,@Max: 验证 Number 和 String 对象是否小等于指定的值,@DecimalMax: 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度@DecimalMin: 被标注的值必须不小于约束中指定的最小值.这个约束的参数是一个通 过BigDecimal定义的最小值的字符串表示.小数存在精度,@Digits: 验证 Number 和 String的构成是否合法,@Digits(integer=,fraction=): 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。

相关检验注解参考:

代码测试

非空检验实体类:

@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic class Student implements Serializable {
private static final long serialVersionUID = -5236977214039498801L; // 基本类型 @NotNull(message = "用户ID不能为空!") private Long userId; // 集合类型 @NotEmpty(message = "地址集合不能为空!") private List
addressId; // 字符串 @NotBlank(message = "备注不能为空!") private String comment;}

BindException异常

测试接口:

@RestController@RequestMapping("/student")@Slf4j@Validated // 可对接口方法的集合参数校验public class StudentController {
/** * 测试空参接口 * @param student * @return */ @PostMapping("/test") public ResponseResult
test(@Valid Student student) {
return new ResponseResult<>(ResponseResult.CodeStatus.OK, "正常访问!"); }}

这里接收参数没有加@RequestBody注解,Postman测试如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KpJHg0aL-1619077558263)(./images/24.png)]

无法正常访问,BindException异常,日志报错如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yu1nHpM6-1619077558265)(./images/23.png)]

解决方法

可以使用BindingResult(可能出现其它异常,代码冗余,不建议)在接口方法中获取message返回或者全局捕获BindException异常进行返回处理。全局捕获BindException异常代码如下:

/** * Title:统一异常,返回json * Description: * @author WZQ * @version 1.0.0 * @date 2021/4/22 */@RestControllerAdvice(annotations = {
RestController.class, Controller.class})@Slf4jpublic class BaseExceptionHandler {
/** * 空参异常处理 * @param ex * @param request * @return */ @ExceptionHandler(BindException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseResult
bindException(BindException ex, HttpServletRequest request) {
log.warn("BindException:", ex); try {
// 拿到@NotNull,@NotBlank和 @NotEmpty等注解上的message值 String msg = Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage(); if (StrUtil.isNotEmpty(msg)) {
// 自定义状态返回 return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, msg); } } catch (Exception ignored) {
} // 参数类型不匹配检验 StringBuilder msg = new StringBuilder(); List
fieldErrors = ex.getFieldErrors(); fieldErrors.forEach((oe) -> msg.append("参数:[").append(oe.getObjectName()) .append(".").append(oe.getField()) .append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配.") ); return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, msg.toString()); }}

再次测试,捕获到异常并返回对应的错误信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-80dj2zt1-1619077558267)(./images/25.png)]

ConstraintViolationException异常

利用BindingResult返回错误信息,测试接口:

@RestController@RequestMapping("/student")@Slf4j@Validated // 可对接口方法的集合参数校验public class StudentController {
/** * 测试空参接口 * @param student * @return */ @PostMapping("/test") public ResponseResult
test(@Valid Student student, BindingResult results) {
if (results.hasErrors()) {
// 没有统一异常的返回,获取注解上的默认message return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, Objects.requireNonNull(results.getFieldError()).getDefaultMessage()); } return new ResponseResult<>(ResponseResult.CodeStatus.OK, "正常访问!"); }}

postman测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TugqWDZY-1619077558270)(./images/21.png)]

不是BindingResult获得的错误信息,无法正常访问,ConstraintViolationException异常,日志报错如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ec0NyWA6-1619077558273)(./images/22.png)]

解决方法1

正确使用@Validated和@Valid注解,由于实体类Student属性有List集合存在,需要结合@Validated和@Valid使用嵌套检验的方式,代码修改如下:

// 基本类型    @NotNull(message = "用户ID不能为空!")    private Long userId;    // 集合类型    @NotEmpty(message = "地址集合不能为空!")    @Valid // @Valid可用于集合嵌套验证    private List
addressId; // 字符串 @NotBlank(message = "备注不能为空!") private String comment;
@RestController@RequestMapping("/student")@Slf4j//@Validated // 可对接口方法的集合参数校验public class StudentController {
/** * 测试空参接口 * @param student * @return */ @PostMapping("/test") public ResponseResult
test(@Validated Student student, BindingResult results) {
if (results.hasErrors()) {
// 没有统一异常的返回,获取注解上的默认message return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, Objects.requireNonNull(results.getFieldError()).getDefaultMessage()); } return new ResponseResult<>(ResponseResult.CodeStatus.OK, "正常访问!"); }}

正常访问:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0rQpW5qh-1619077558274)(./images/26.png)]

@Validated和@Valid的区别和使用,包括嵌套检验可以参考:

解决方法2

同样也可以全局捕获ConstraintViolationException异常并返回错误信息,错误信息是校验注解上的默认message。全局捕获ConstraintViolationException异常代码如下:

/**     * jsr 规范中的验证异常,嵌套检验问题     * @param ex     * @return     */    @ExceptionHandler(ConstraintViolationException.class)    @ResponseStatus(HttpStatus.BAD_REQUEST)    public ResponseResult
constraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
log.warn("ConstraintViolationException:", ex); Set
> violations = ex.getConstraintViolations(); String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));// ConstraintViolation
violation = violations.iterator().next();// String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();// String message2 = String.format("%s:%s", path, violation.getMessage()); return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, message); }

postman测试如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qAQXIJ0w-1619077558276)(./images/27.png)]

MethodArgumentNotValidException异常

BindingResult实际开发不常用,不可能每次都要在接口检测并返回错误信息,代码冗余。但是接口不加BindingResult参数又会出现异常。

@RestController@RequestMapping("/student")@Slf4j//@Validated // 可对接口方法的集合参数校验public class StudentController {
/** * 测试空参接口 * @param student * @return */ @PostMapping("/test") public ResponseResult
test(@RequestBody @Validated Student student) {
return new ResponseResult<>(ResponseResult.CodeStatus.OK, "正常访问!"); }}

注意,这里接收参数加上@RequestBody注解才会有这种异常,Postman测试如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dhRz7vPB-1619077558277)(./images/28.png)]

直接400返回,跟MethodArgumentNotValidException异常有关,日志信息如下:

2021-04-22 15:25:27.051  WARN 18068 --- [nio-7001-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument ...

解决方法

同样,全局捕获MethodArgumentNotValidException异常并返回错误信息,错误信息是校验注解上的默认message。全局捕获ConstraintViolationException异常代码如下:

/**     * spring 封装的参数验证异常, 在controller中没有写BindingResult参数时,会进入     * @param ex     * @return     */    @ExceptionHandler(MethodArgumentNotValidException.class)    @ResponseStatus(HttpStatus.BAD_REQUEST)    public ResponseResult
methodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
log.warn("MethodArgumentNotValidException:", ex); return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage()); }

测试如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jr1lrj1T-1619077558278)(./images/29.png)]

统一异常处理完整代码

Spring validation入参验证框架,一般在Controller类加上@Validated注解(可检验集合参数),接口方法对应的dto加上@Valid注解,然后直接对以上三个异常进行全局捕获处理即可。完整代码如下:

import cn.hutool.core.util.StrUtil;import com.course.commons.dto.ResponseResult;import lombok.extern.slf4j.Slf4j;import org.hibernate.validator.internal.engine.path.PathImpl;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Controller;import org.springframework.validation.BindException;import org.springframework.validation.FieldError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseStatus;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.servlet.http.HttpServletRequest;import javax.validation.ConstraintViolation;import javax.validation.ConstraintViolationException;import java.util.List;import java.util.Objects;import java.util.Set;import java.util.stream.Collectors;/** * Title:统一异常,返回json * Description: * @author WZQ * @version 1.0.0 * @date 2021/4/22 */@RestControllerAdvice(annotations = {
RestController.class, Controller.class})@Slf4jpublic class BaseExceptionHandler {
/** * 空参异常处理 * @param ex * @param request * @return */ @ExceptionHandler(BindException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseResult
bindException(BindException ex, HttpServletRequest request) {
log.warn("BindException:", ex); try {
// 拿到@NotNull,@NotBlank和 @NotEmpty等注解上的message值 String msg = Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage(); if (StrUtil.isNotEmpty(msg)) {
// 自定义状态返回 return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, msg); } } catch (Exception ignored) {
} // 参数类型不匹配检验 StringBuilder msg = new StringBuilder(); List
fieldErrors = ex.getFieldErrors(); fieldErrors.forEach((oe) -> msg.append("参数:[").append(oe.getObjectName()) .append(".").append(oe.getField()) .append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配.") ); return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, msg.toString()); } /** * jsr 规范中的验证异常,嵌套检验问题 * @param ex * @return */ @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseResult
constraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
log.warn("ConstraintViolationException:", ex); Set
> violations = ex.getConstraintViolations(); String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));// ConstraintViolation
violation = violations.iterator().next();// String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();// String message2 = String.format("%s:%s", path, violation.getMessage()); return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, message); } /** * spring 封装的参数验证异常, 在controller中没有写result参数时,会进入 * @param ex * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseResult
methodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
log.warn("MethodArgumentNotValidException:", ex); return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage()); }}

转载地址:http://gviwi.baihongyu.com/

你可能感兴趣的文章
判断二叉树是否有从根节点到叶子节点的节点值之和等于sum的路径
查看>>
反转字符串
查看>>
环形链表
查看>>
删除链表的倒数第N个节点
查看>>
回文链表
查看>>
容器盛水问题
查看>>
滑动窗口最大值
查看>>
win7 文件删除后要刷新后才会消失
查看>>
用ffmpeg转多音轨的mkv文件
查看>>
ubuntu12.04 安装VLC,在root用户下不能使用的问题
查看>>
简单而又完整的Makefile
查看>>
GNU/Linux下如何卸载源码安装的软件
查看>>
ffmpeg 常用 命令随手记
查看>>
av_seek_frame中flags值的意义
查看>>
git 学习笔记
查看>>
C++类中的static的用法
查看>>
vector 释放内存 swap
查看>>
在linux下新增一块硬盘的操作。(包含大于2T的硬盘在linux下挂载操作)
查看>>
在32位系统中使用fseek和lseek或fwrite、write写大文件时,最大只能写2G左右的解决办法
查看>>
整理华为C/C++编码规范
查看>>