本文共 11394 字,大约阅读时间需要 37 分钟。
Spring validation验证框架提供了大量接口入参检验注解,注意三个非空注解:
相关检验注解参考:
非空检验实体类:
@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic class Student implements Serializable { private static final long serialVersionUID = -5236977214039498801L; // 基本类型 @NotNull(message = "用户ID不能为空!") private Long userId; // 集合类型 @NotEmpty(message = "地址集合不能为空!") private ListaddressId; // 字符串 @NotBlank(message = "备注不能为空!") private String comment;}
测试接口:
@RestController@RequestMapping("/student")@Slf4j@Validated // 可对接口方法的集合参数校验public class StudentController { /** * 测试空参接口 * @param student * @return */ @PostMapping("/test") public ResponseResulttest(@Valid Student student) { return new ResponseResult<>(ResponseResult.CodeStatus.OK, "正常访问!"); }}
这里接收参数没有加@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})@Slf4jpublic class BaseExceptionHandler { /** * 空参异常处理 * @param ex * @param request * @return */ @ExceptionHandler(BindException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseResultbindException(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()); }}
再次测试,捕获到异常并返回对应的错误信息:
利用BindingResult返回错误信息,测试接口:
@RestController@RequestMapping("/student")@Slf4j@Validated // 可对接口方法的集合参数校验public class StudentController { /** * 测试空参接口 * @param student * @return */ @PostMapping("/test") public ResponseResulttest(@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测试:
不是BindingResult获得的错误信息,无法正常访问,ConstraintViolationException异常,日志报错如下:
正确使用@Validated和@Valid注解,由于实体类Student属性有List集合存在,需要结合@Validated和@Valid使用嵌套检验的方式,代码修改如下:
// 基本类型 @NotNull(message = "用户ID不能为空!") private Long userId; // 集合类型 @NotEmpty(message = "地址集合不能为空!") @Valid // @Valid可用于集合嵌套验证 private ListaddressId; // 字符串 @NotBlank(message = "备注不能为空!") private String comment;
@RestController@RequestMapping("/student")@Slf4j//@Validated // 可对接口方法的集合参数校验public class StudentController { /** * 测试空参接口 * @param student * @return */ @PostMapping("/test") public ResponseResulttest(@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, "正常访问!"); }}
正常访问:
@Validated和@Valid的区别和使用,包括嵌套检验可以参考:
同样也可以全局捕获ConstraintViolationException异常并返回错误信息,错误信息是校验注解上的默认message。全局捕获ConstraintViolationException异常代码如下:
/** * jsr 规范中的验证异常,嵌套检验问题 * @param ex * @return */ @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseResultconstraintViolationException(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测试如下:
BindingResult实际开发不常用,不可能每次都要在接口检测并返回错误信息,代码冗余。但是接口不加BindingResult参数又会出现异常。
@RestController@RequestMapping("/student")@Slf4j//@Validated // 可对接口方法的集合参数校验public class StudentController { /** * 测试空参接口 * @param student * @return */ @PostMapping("/test") public ResponseResulttest(@RequestBody @Validated Student student) { return new ResponseResult<>(ResponseResult.CodeStatus.OK, "正常访问!"); }}
注意,这里接收参数加上@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 ...
同样,全局捕获MethodArgumentNotValidException异常并返回错误信息,错误信息是校验注解上的默认message。全局捕获ConstraintViolationException异常代码如下:
/** * spring 封装的参数验证异常, 在controller中没有写BindingResult参数时,会进入 * @param ex * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseResultmethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) { log.warn("MethodArgumentNotValidException:", ex); return new ResponseResult<>(ResponseResult.CodeStatus.FAIL, Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage()); }
测试如下:
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 ResponseResultbindException(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/