本文主要总结了在web
开发中、在进行http
请求时、对结果返回值的统一封装以及常见的一些异常,如参数缺失、参数校验失败、方法类型不匹配等异常的统一处理。配合Spring-Validated的入门级使用更加。
- Spring boot 返回值+异常统一处理
- 参数验证常见的一些异常
统一返回值处理
在Java开发中,我们常常需要对返回结果进行统一处理、在没有进行处理时、我们一般会定义一个泛型类,如:
public class ResultData <T>{
private String code;
private String msg;
private T data;
public static <T> ResultData<T> success(){
return new ResultData<T>("200","success",null);
}
public static <T> ResultData<T> success(T data){
return new ResultData<T>("200","success",data);
}
public static <T> ResultData<T> fail(String code,String msg){
return new ResultData<T>(code,msg,null);
}
}
然后在Controller中
通过如下的方式使用:
public ResultData<User> getDataDetail(@Validated @RequestBody RequestArgs requestArgs){
User result = userService.loadData(requestArgs)
return result == null ?ResultData.fail("500","服务异常")
:ResultData.success(result);
}
基本上在每一个handler方法
中都需要调用ResultData.success(result)
这一行代码,为了不重复编码、可以通过实现ResponseBodyAdvice
的方式去实现这一个功能。
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if(o instanceof String){
return objectMapper.writeValueAsString(ResultData.success(o));
}
if(o instanceof ResultData ){
return o;
}
if(o instanceof PageResultData ){
return o;
}
}
}
ResponseBodyAdvice
是 Spring Framework 中的一个接口,允许我们在每个控制器方法的响应体被发送回客户端之前,对其进行修改或处理。supports()
: 判断是否需要对响应体进行处理。beforeBodyWrite()
: 在响应体写出之前对其进行处理。
如果是String
类型的、需要json
化、否则会发生重定向。如果是我们定义的ResultData
或者PageResultData
,则直接返回。
统一异常处理
当服务发生异常时、我们常见的是在业务代码里面进行try catch
处理,Spring
提供了@RestControllerAdvice
注解,该注解通常用于全局处理控制器抛出的异常,并将这些异常转换为 HTTP 响应。它是 @ControllerAdvice
注解的扩展。使用方式如下:
package com.validate.tutorial.global;
/**
* 创建时间: 2024年09月13日 17:04
* 文件描述:
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 功能: 捕获 404 异常
* @param e
* @return Object
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
public Object handlerNoHandleFoundException(NoHandlerFoundException e) {
log.error("请求路径不存在 {}", e.getHttpMethod());
return ResultData.fail("404", "请求路径不存在");
}
/**
* 功能: 缺少参数异常
* @param e
* @return Object
*/
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public Object handlerMissingServletRequestParameterException(MissingServletRequestParameterException e) {
log.error("缺少参数 {}", e.getParameterName());
return ResultData.fail("400", "缺少参数 ["+e.getParameterName()+"]");
}
/**
* 功能: 请求方法不匹配抛出的异常
*
* @param e
* @return Object
*/
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Object handlerHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.error("不支持的请求方式 {}", e.getMessage());
return ResultData.fail("400", "不支持的请求方式");
}
/**
* 功能: 请求参数解析异常 @RequestBody 抛出
*
* @param e
* @return Object
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public Object handlerHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error("请求参数解析异常 {}", e.getMessage());
return ResultData.fail("400", "请求参数解析异常");
}
/**
* 功能: 参数类型不匹配异常
*
* @param e
* @return Object
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public Object handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
log.error("参数类型不匹配异常", e.getMessage());
return ResultData.fail("400", "参数类型不匹配异常");
}
/**
* 功能: 参数绑定异常 @Valid 单独使用抛出 如:(@Valid RequestArgs requestArgs) RequestArgs使用了@NotNull @Max @Min等注解
* @Valid+@RequestBody也会进入此逻辑,如参数缺失 校验失败等
* @Validate 是对 @Valid 的封装
* @date 2024/9/15 17:09
* @param e
* @return Object
*/
@ExceptionHandler(BindException.class)
public Object handleBindException(BindException e) {
log.error("BindException {}", e.getMessage());
return ResultData.fail("400", "BindException");
}
/**
* 功能: 参数校验异常 @Valid + @RequestBody 参数校验失败会进入此异常,如果同时写了BindException 会进入这个异常
*
* @date 2024/9/15 17:09
* @param e
* @return Object
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handlerMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("MethodArgumentNotValidException {}", e.getMessage());
return ResultData.fail("400", "参数校验异常");
}
/**
* 功能: singleParameter2(@NotBlank String username, @NotNull Integer userId) 需要在类上 @Validate
* 此时参数校验失败会进入此异常
*
* @param e
* @return Object
*/
@ExceptionHandler(ConstraintViolationException.class)
public Object handlerConstraintViolationException(ConstraintViolationException e){
log.error("ConstraintViolationException {}", e.getMessage());
return ResultData.fail("400", "参数校验异常");
}
@ExceptionHandler(value = Exception.class)
public Object handlerException(Exception e) {
log.error("系统异常 {}", e.getMessage());
return ResultData.fail("500", "系统异常");
}
}
详细说明如下:
-
NoHandlerFoundException
:这个异常发生的场景是后端没有响应的请求接口时触发。@ExceptionHandler(value = NoHandlerFoundException.class) public Object handlerNoHandleFoundException(NoHandlerFoundException e) { log.error("请求路径不存在 {}", e.getHttpMethod()); return ResultData.fail("404", "请求路径不存在"); }
需要在统一异常处理时需要在配置文件中添加如下配置,才能被捕获。
spring:
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
MissingServletRequestParameterException
: 参数缺失异常,如果有如下接口,参数username
或者password
没有传递就会触发此异常。
@GetMapping("missing_servlet_request_parameter_exception.do")
public ResultData<String> testMissingServletRequestParameterException(@RequestParam String username,
@RequestParam String password) {
return ResultData.success();
}
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public Object handlerMissingServletRequestParameterException(MissingServletRequestParameterException e) {
log.error("缺少参数 {}", e.getParameterName());
return ResultData.fail("400", "缺少参数 ["+e.getParameterName()+"]");
}
-
HttpRequestMethodNotSupportedException
:当请求方法不匹配时,就会触发此异常,如使用@GetMapping
修饰的handler方法使用post方式去访问@ResponseStatus(value = HttpStatus.BAD_REQUEST) @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public Object handlerHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { log.error("不支持的请求方式 {}", e.getMessage()); return ResultData.fail("400", "不支持的请求方式"); }
-
HttpMessageNotReadableException
:当后端使用@RequestBody修饰方法时,前端传参是使用FormData方式时,就会触发这个异常@ExceptionHandler(HttpMessageNotReadableException.class) public Object handlerHttpMessageNotReadableException(HttpMessageNotReadableException e) { log.error("请求参数解析异常 {}", e.getMessage()); return ResultData.fail("400", "请求参数解析异常"); }
-
MethodArgumentTypeMismatchException
:当前后端参数类型不匹配时抛出此异常。// 如需要一个Integer传递了一个字符串 @GetMapping("method_argument_type_mismatch_exception.do") public ResultData<String> testMethodArgumentTypeMismatchException(Integer number){ return ResultData.success("testMethodArgumentTypeMismatchException"); } @ExceptionHandler(MethodArgumentTypeMismatchException.class) public Object handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { log.error("参数类型不匹配异常", e.getMessage()); return ResultData.fail("400", "参数类型不匹配异常"); }
-
BindException、MethodArgumentNotValidException
: 参数绑定异常,如使用了validation
第三方工具包使用@Validated
修饰方法时,方法1就会触发BindException
,方法2的参数校验不成功时就会触发MethodArgumentNotValidException
。public class RequestArgs { //@NotNull(groups = CostumerValidGroup.Curd.Update.class, message = "userId不能为null") //@Null(groups = CostumerValidGroup.Curd.Insert.class) private Integer userId; @NotBlank(message = "名称为必填项") private String nickName; @Email(message = "请填写正确的邮箱地址") private String email; @Length(message = "密码长度应在6~12位之间", min = 6, max = 12) @NotBlank private String password; } // controller @PostMapping("valid_bind_exception1.do") public ResultData testValidBindException1(@Validated RequestArgs requestArgs){ log.info("BindException requestArgs = {}", requestArgs); return ResultData.success(); } @PostMapping("valid_bind_exception2.do") public ResultData testValidBindException2(@Validated @RequestBody RequestArgs requestArgs){ log.info("testValidBindException requestArgs = {}", requestArgs); return ResultData.success(); } // globalHandler.java @ExceptionHandler(BindException.class) public Object handleBindException(BindException e) { log.error("BindException {}", e.getMessage()); return ResultData.fail("400", "BindException"); } @ExceptionHandler(MethodArgumentNotValidException.class) public Object handlerMethodArgumentNotValidException(MethodArgumentNotValidException e) { log.error("MethodArgumentNotValidException {}", e.getMessage()); return ResultData.fail("400", "参数校验异常"); }
-
ConstraintViolationException
: 方法参数如(@NotBlank String username, @NotNull Integer userId)
需要在类上@Validated
此时参数校验失败会进入此异常@GetMapping("single_param2.do") public void singleParameter2(@NotBlank String username, @NotNull Integer userId) { log.info("singleParameter2 username = {},userId = {}", username, userId); } // 如username:""就会触发此异常。 @ExceptionHandler(ConstraintViolationException.class) public Object handlerConstraintViolationException(ConstraintViolationException e){ log.error("ConstraintViolationException {}", e.getMessage()); return ResultData.fail("400", "参数校验异常"); }