[springboot]通用全局异常处理逻辑(带视频)

通用全局异常处理逻辑

一、通用异常处理逻辑

程序员的异常处理逻辑要十分的单一:无论在Controller层、Service层还是什么其他位置,程序员只负责一件事:那就是捕获异常,并将异常转换为自定义异常。使用用户友好的信息去填充CustomException的message,并将CustomException抛出去。

@Service
public class ExceptionService {
    
    

    //服务层,模拟系统异常
    public void systemBizError() {
    
    
        try {
    
    
            Class.forName("com.mysql.jdbc.xxxx.Driver");
        } catch (ClassNotFoundException e) {
    
    
            throw new CustomException(
                    CustomExceptionType.SYSTEM_ERROR,
                    "在XXX业务,myBiz()方法内,出现ClassNotFoundException,请将该信息告知管理员");
        }
    }

    //服务层,模拟用户输入数据导致的校验异常
    public void userBizError(int input)  {
    
    
        if(input < 0){
    
     //模拟业务校验失败逻辑
            throw new CustomException(
                    CustomExceptionType.USER_INPUT_ERROR,
                    "您输入的数据不符合业务逻辑,请确认后重新输入!");
        }

        //…… 其他的业务
    }

}

二、全局异常处理器

通过团队内的编码规范的要求,我们已经知道了:不允许程序员截留处理Exception,必须把异常转换为自定义异常CustomException全都抛出去。那么程序员把异常跑出去之后由谁来处理?那就是ControllerAdvice。
ControllerAdvice注解的作用就是监听所有的Controller,一旦Controller抛出CustomException,就会在@ExceptionHandler(CustomException.class)注解的方法里面对该异常进行处理。处理方法很简单就是使用AjaxResponse.error(e)包装为通用的接口数据结构返回给前端。

@ControllerAdvice
public class WebExceptionHandler {
    
    

    //处理程序员主动转换的自定义异常
    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public AjaxResponse customerException(CustomException e) {
    
    
        if(e.getCode() == CustomExceptionType.SYSTEM_ERROR.getCode()){
    
    
                 //400异常不需要持久化,将异常信息以友好的方式告知用户就可以
                //TODO 将500异常信息持久化处理,方便运维人员处理
        }
        return AjaxResponse.error(e);
    }

    //处理程序员在程序中未能捕获(遗漏的)异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public AjaxResponse exception(Exception e) {
    
    
        //TODO 将异常信息持久化处理,方便运维人员处理

        return AjaxResponse.error(new CustomException(
                CustomExceptionType.OTHER_ERROR));
    }


}

三、测试一下

随便找一个API,注入ExceptionService访问测试一下
在这里插入图片描述

当id=1的数据响应结果
在这里插入图片描述

当id!=1的数据响应结果
在这里插入图片描述

四、业务状态与HTTP协议状态一致

不知道大家有没有注意到一个问题(看上图)?这个问题就是我们的AjaxResponse的code是400,但是真正的HTTP协议状态码是200?
通说的说,目前

  • AjaxResponse的code是400代表的是业务状态,也就是说用户的请求业务失败了
  • 但是HTTP请求是成功的,也就是说数据是正常返回的。

在很多的公司开发RESTful服务时,要求HTTP状态码能够体现业务的最终执行状态,所以说:我们有必要让业务状态与HTTP协议Response状态码一致。

@Component
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {
    
    
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
    
    
        //return returnType.hasMethodAnnotation(ResponseBody.class);
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
    
    

        //如果响应结果是JSON数据类型
        if(selectedContentType.equalsTypeAndSubtype(
                MediaType.APPLICATION_JSON)){
    
    
                //为HTTP响应结果设置状态码,状态码就是AjaxResponse的code,二者达到统一
                response.setStatusCode(
                        HttpStatus.valueOf(((AjaxResponse) body).getCode())
                );
                return body;
        }
        return body;
    }
}

实现ResponseBodyAdvice 接口的作用是:在将数据返回给用户之前,做最后一步的处理。也就是说,ResponseBodyAdvice 的处理过程在全局异常处理的后面。

效果如下:
在这里插入图片描述

五、进一步优化

我们已经知道了,ResponseBodyAdvice 接口的作用是:在将数据返回给用户之前,做最后一步的处理。将上文的GlobalResponseAdvice 中beforeBodyWrite方法代码优化如下。

在这里插入图片描述

  • 如果Controller或全局异常处理响应的结果body是AjaxResponse,就直接return给前端。
  • 如果Controller或全局异常处理响应的结果body不是AjaxResponse,就将body封装为AjaxResponse之后再return给前端。

所以,我们之前的代码是这样写的,比如:某个controller方法返回值

return AjaxResponse.success(objList);

现在就可以这样写了,因为在GlobalResponseAdvice 里面会统一再封装为AjaxResponse。

return objList;

最终代码如下:

@Component
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {
    
    

  @Override
  public boolean supports(MethodParameter methodParameter, Class aClass) {
    
    
    return true;
  }

  @Override
  public Object beforeBodyWrite(Object body,
                                MethodParameter methodParameter,
                                MediaType mediaType,
                                Class aClass,
                                ServerHttpRequest serverHttpRequest,
                                ServerHttpResponse serverHttpResponse) {
    
    
    //如果响应结果是JSON数据类型
    if(mediaType.equalsTypeAndSubtype(
            MediaType.APPLICATION_JSON)){
    
    
      if(body instanceof AjaxResponse){
    
    
        AjaxResponse ajaxResponse = (AjaxResponse)body;
        if(ajaxResponse.getCode() != 999){
    
     //999 不是标准的HTTP状态码,特殊处理
          serverHttpResponse.setStatusCode(HttpStatus.valueOf(
                  ajaxResponse.getCode()
          ));
        }
        
        return body;
      }else{
    
    
        serverHttpResponse.setStatusCode(HttpStatus.OK);
        return AjaxResponse.success(body);
      }

    }


    return body;
  }
}

猜你喜欢

转载自blog.csdn.net/hanxiaotongtong/article/details/122892972