首页 > 其他分享 >BindException、ConstraintViolationException、MethodArgumentNotValidException入参验证异常分析和全局异常处理解决方法

BindException、ConstraintViolationException、MethodArgumentNotValidException入参验证异常分析和全局异常处理解决方法

时间:2023-11-07 15:11:06浏览次数:28  
标签:return String ConstraintViolationException ResponseResult BindException ex impor

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指定小数精度。
相关检验注解参考:

https://blog.csdn.net/qq_45151158/article/details/103126907

代码测试
非空检验实体类:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {

private static final long serialVersionUID = -5236977214039498801L;

// 基本类型
@NotNull(message = "用户ID不能为空!")
private Long userId;

// 集合类型
@NotEmpty(message = "地址集合不能为空!")
private List<String> addressId;

// 字符串
@NotBlank(message = "备注不能为空!")
private String comment;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BindException异常
测试接口:

@RestController
@RequestMapping("/student")
@Slf4j
@Validated // 可对接口方法的集合参数校验
public class StudentController {

/**
* 测试空参接口
* @param student
* @return
*/
@PostMapping("/test")
public ResponseResult<Void> test(@Valid Student student) {
return new ResponseResult<>(ResponseResult.CodeStatus.OK, "正常访问!");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这里接收参数没有加@RequestBody注解,Postman测试如下:

 

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

 

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

/**
* Title:统一异常,返回json
* Description:
* @author WZQ
* @version 1.0.0
* @date 2021/4/22
*/
@RestControllerAdvice(annotations = {RestController.class, Controller.class})
@Slf4j
public class BaseExceptionHandler {

/**
* 空参异常处理
* @param ex
* @param request
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseResult<Void> 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<FieldError> 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());
}

}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
再次测试,捕获到异常并返回对应的错误信息:

 

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

@RestController
@RequestMapping("/student")
@Slf4j
@Validated // 可对接口方法的集合参数校验
public class StudentController {

/**
* 测试空参接口
* @param student
* @return
*/
@PostMapping("/test")
public ResponseResult<Void> 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, "正常访问!");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
postman测试:

 

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

 

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

// 基本类型
@NotNull(message = "用户ID不能为空!")
private Long userId;

// 集合类型
@NotEmpty(message = "地址集合不能为空!")
@Valid // @Valid可用于集合嵌套验证
private List<String> addressId;

// 字符串
@NotBlank(message = "备注不能为空!")
private String comment;
1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/student")
@Slf4j
//@Validated // 可对接口方法的集合参数校验
public class StudentController {

/**
* 测试空参接口
* @param student
* @return
*/
@PostMapping("/test")
public ResponseResult<Void> 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, "正常访问!");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
正常访问:

 

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

https://blog.csdn.net/qq_27680317/article/details/79970590

https://blog.csdn.net/qq_45151158/article/details/112349233?spm=1001.2014.3001.5501

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

/**
* jsr 规范中的验证异常,嵌套检验问题
* @param ex
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseResult<Void> constraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
log.warn("ConstraintViolationException:", ex);
Set<ConstraintViolation<?>> 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
postman测试如下:

 

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

@RestController
@RequestMapping("/student")
@Slf4j
//@Validated // 可对接口方法的集合参数校验
public class StudentController {

/**
* 测试空参接口
* @param student
* @return
*/
@PostMapping("/test")
public ResponseResult<Void> test(@RequestBody @Validated Student student) {
return new ResponseResult<>(ResponseResult.CodeStatus.OK, "正常访问!");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
注意,这里接收参数加上@RequestBody注解才会有这种异常,Postman测试如下:

 

直接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 ...
1
解决方法
同样,全局捕获MethodArgumentNotValidException异常并返回错误信息,错误信息是校验注解上的默认message。全局捕获MethodArgumentNotValidException异常代码如下:

/**
* spring 封装的参数验证异常, 在controller中没有写BindingResult参数时,会进入
* @param ex
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseResult<Void> methodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
log.warn("MethodArgumentNotValidException:", ex);
return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage());
}
1
2
3
4
5
6
7
8
9
10
11
测试如下:

 

统一异常处理完整代码
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})
@Slf4j
public class BaseExceptionHandler {

/**
* 空参异常处理
* @param ex
* @param request
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseResult<Void> 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<FieldError> 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<Void> constraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
log.warn("ConstraintViolationException:", ex);
Set<ConstraintViolation<?>> 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<Void> methodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
log.warn("MethodArgumentNotValidException:", ex);
return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage());
}
}
————————————————
版权声明:本文为CSDN博主「wzq_55552」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43409401/article/details/116017177

标签:return,String,ConstraintViolationException,ResponseResult,BindException,ex,impor
From: https://www.cnblogs.com/cytc/p/17815061.html

相关文章

  • selenium等待元素加载,元素操作,执行js,切换选项卡,前进后退,异常处理,登录cnblogs,抽
    1selenium等待元素加载......
  • Util应用框架基础(五) - 异常处理
    本节介绍Util应用框架如何处理系统错误.概述系统在运行过程中可能发生错误.系统错误可以简单分为两类:系统异常系统本身出现的错误.业务异常不满足业务规则出现的错误.如何处理系统异常如果发生系统异常,大多数情况下,你除了记录异常日志外,可能无法处理它们.一个......
  • Eolink Apikit 如何对所有 API 异常请求实时监控 ?
    API监控适合业务在互联网上,并且用户来自多个不同的地区,且对API的要求较高的场合,用于解决以下的问题:发现由于网络中断或者是API响应异常等导致的服务不可用;及时对异常的API进行告警;记录监控的日志,方便排查。EolinkApikit除了提供API的管理和自动化测试等功能,还提供API......
  • 异常详细信息: System.Web.HttpException: 服务器太忙
    HTTP双连接限制HTTP规范表明,一个HTTP客户端与任一服务器最多可以同时建立两个TCP连接。这可以防止单个浏览器在浏览某个页面(例如,具有120个嵌入的缩略图)时,由于连接请求过多而使服务器负载过重。此时,浏览器将仅创建2个连接,然后通过这两个管道开始发送120个HTTP请求,而......
  • C#开发的软件在Windows7中出现对路径的访问被拒绝异常
    C#开发的软件在Windows7中出现对路径的访问被拒绝异常在VS2008/VS2010下,右键项目=>属性=>安全性=>直接勾选“启用ClickOnce安全设置”即可解决问题。 创建文件夹和文件时,选择其他盘,比如:D,E,F.不要选择创建到C盘。......
  • 【Azure Function App】解决Function App For Container 遇见ServiceUnavailable的异
    问题描述在使用Terraform创建FunctionApp后,部署函数时候遇见 ServiceUnavailable(BadRequest-- Encounteredanerror(ServiceUnavailable)fromhostruntime.)问题。查看FunctionApp的高级工具(Kudu)站点和默认站点,均出现ApplicationError页面。 问题解答查看Function......
  • 【Azure Function App】解决Function App For Container 遇见ServiceUnavailable的异
    问题描述在使用Terraform创建FunctionApp后,部署函数时候遇见 ServiceUnavailable(BadRequest-- Encounteredanerror(ServiceUnavailable)fromhostruntime.)问题。查看FunctionApp的高级工具(Kudu)站点和默认站点,均出现ApplicationError页面。 问题解答查看F......
  • 异常Couldn’t connect to host, port: smtp.qq.com, 25
    com.sun.mail.util.MailConnectException:Couldn’tconnecttohost,port:smtp.qq.com,25;timeout-1阿里云处于安全考虑,TCP25端口默认被封禁。可以向阿里云申请解封,也可以改为ssl加密465端口发送。465端口发送主要代码:Propertiesprops=newProperties();props.......
  • 异常检测算法-完全卷积数据描述子FCDD
    文献来源:EXPLAINABLEDEEPONE-CLASSCLASSIFICATION   最近在做一些异物检测之类的算法任务,原本想使用目标识别算法,但是问题是正样本太多,而负样本没几个。所以有必要使用异常检测算法,日后不妨再结合目标识别任务去做。在正式开始前,需要先简单介绍一个广义损失函数的东西......
  • 文件 inode 与 no space left on device 异常
    转载请注明出处:文件inode在Linux文件系统中,每一个文件或目录都会有一个inode,它是一个数据结构,用于存储文件的元数据,比如文件的权限、所有者、大小、创建和修改的时间等。inode不包含文件的实际内容,只包含文件的元数据。当你在文件系统中创建、修改或者删除文件时,实际上......