SpringBoot统一处理返回结果和异常情况

567 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情

统一异常处理和发挥结果的原因

前后端分离的时代,如果没有统一的返回格式,给前端的结果各式各样,估计前端的小伙伴就要骂娘了。
我们想对自定义异常抛出指定的状态码排查错误,对系统的不可预知的异常抛出友好一点的异常信息。
我们想让接口统一返回一些额外的数据,例如接口执行的时间等等。
...
所以,目前的前后端开发大部分数据的传输格式都是json,定义一个统一规范的数据格式有利于前后端的交互与UI的展示。

统一结果的一般形式

  1. 是否响应成功
  2. 响应状态码
  3. 状态码描述
  4. 响应数据
  5. 其他标识符 前三者可定义结果枚举,如:success,code,message 比如响应状态码可以如下定义
  • 200: 请求处理成功
  • 400: 请求处理失败
  • 500: 服务器内部错误
  • 401未认证(签名错误)
  • 404接口不存在
public enum RetCode {
   // 成功
   SUCCESS(200),
   // 失败
   FAIL(400),
   // 未认证(签名错误)
   UNAUTHORIZED(401),
   // 接口不存在
   NOT_FOUND(404),
   // 服务器内部错误
   INTERNAL_SERVER_ERROR(500);
   public int code;
   RetCode(int code) {
      this.code = code;
   }
}

第5个属于自定义返回,利用前4者可定义统一返回对象

创建返回对象实体(泛型)

public class RetResult<T> {
   public int code;
   private String msg;
   private T data;
   public RetResult<T> setCode(RetCode retCode) {
      this.code = retCode.code;
      return this;
   }
   public int getCode() {
      return code;
   }
   public RetResult<T> setCode(int code) {
      this.code = code;
      return this;
   }
   public String getMsg() {
      return msg;
   }
   public RetResult<T> setMsg(String msg) {
      this.msg = msg;
      return this;
   }
   public T getData() {
      return data;
   }
   public RetResult<T> setData(T data) {
      this.data = data;
      return this;
   }
}

说明:code为状态码、msg为提示信息、data为返回的数据

返回结果数据格式封装

  1. 外接只可以调用统一返回类的方法,不可以直接创建,影刺构造器私有;
  2. 内置静态方法,返回对象;
  3. 为便于自定义统一结果的信息,建议使用链式编程,将返回对象设类本身,即return this;
  4. 响应数据由于为json格式,可定义为JsonObject或Map形式;
public class RetResponse {
 
   private final static String SUCCESS = "success";
   public static <T> RetResult<T> makeOKRsp() {
      return new RetResult<T>().setCode(RetCode.SUCCESS).setMsg(SUCCESS);
   }
   public static <T> RetResult<T> makeOKRsp(T data) {
      return new RetResult<T>().setCode(RetCode.SUCCESS).setMsg(SUCCESS).setData(data);
   }
   public static <T> RetResult<T> makeErrRsp(String message) {
      return new RetResult<T>().setCode(RetCode.FAIL).setMsg(SUCCESS);
   }
   public static <T> RetResult<T> makeRsp(int code, String msg) {
      return new RetResult<T>().setCode(code).setMsg(msg);
   }
   public static <T> RetResult<T> makeRsp(int code, String msg, T data) {
      return new RetResult<T>().setCode(code).setMsg(msg).setData(data);
   }
}

统一异常处理

使用统一返回结果时,还有一种情况,就是程序的保存是由于运行时异常导致的结果,有些异常我们可以无法提前预知,不能正常走到我们return的R对象返回。

因此,我们需要定义一个统一的全局异常来捕获这些信息,并作为一种结果返回控制层

@ControllerAdvice

该注解为统一异常处理的核心

是一种作用于控制层的切面通知(Advice),该注解能够将通用的@ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一个类型,并应用到所有控制器上

该类中的设计思路:

  1. 使用@ExceptionHandler注解捕获指定或自定义的异常;
  2. 使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
  3. 必须定义一个通用的异常捕获方法,便于捕获未定义的异常信息;
  4. 自定一个异常类,捕获针对项目或业务的异常;
  5. 异常的对象信息补充到统一结果枚举中;

自定义全局异常类

@Data
public class CMSException extends RuntimeException {
    private Integer code;

    public CMSException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public CMSException(ResultCodeEnum resultCodeEnum) {
        super(resultCodeEnum.getMessage());
        this.code = resultCodeEnum.getCode();
    }

    @Override
    public String toString() {
        return "CMSException{" + "code=" + code + ", message=" + this.getMessage() + '}';
    }
}

统一异常处理器

// ...
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalExceptionHandler {

    /**-------- 通用异常处理方法 --------**/
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e) {
        e.printStackTrace();
        return R.error(); // 通用异常结果
    }

    /**-------- 指定异常处理方法 --------**/
    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public R error(NullPointerException e) {
        e.printStackTrace();
        return R.setResult(ResultCodeEnum.NULL_POINT);
    }

    @ExceptionHandler(HttpClientErrorException.class)
    @ResponseBody
    public R error(IndexOutOfBoundsException e) {
        e.printStackTrace();
        return R.setResult(ResultCodeEnum.HTTP_CLIENT_ERROR);
    }
    
    /**-------- 自定义定异常处理方法 --------**/
    @ExceptionHandler(CMSException.class)
    @ResponseBody
    public R error(CMSException e) {
        e.printStackTrace();
        return R.error().message(e.getMessage()).code(e.getCode());
    }
}

控制层展示

以下为展示当遇到null指定异常时,返回的结果信息

{
  "success": false,
  "code": 505,
  "message": "空指针异常",
  "data": {}
}