持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情
统一异常处理和发挥结果的原因
前后端分离的时代,如果没有统一的返回格式,给前端的结果各式各样,估计前端的小伙伴就要骂娘了。
我们想对自定义异常抛出指定的状态码排查错误,对系统的不可预知的异常抛出友好一点的异常信息。
我们想让接口统一返回一些额外的数据,例如接口执行的时间等等。
...
所以,目前的前后端开发大部分数据的传输格式都是json,定义一个统一规范的数据格式有利于前后端的交互与UI的展示。
统一结果的一般形式
- 是否响应成功
- 响应状态码
- 状态码描述
- 响应数据
- 其他标识符 前三者可定义结果枚举,如: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为返回的数据
返回结果数据格式封装
- 外接只可以调用统一返回类的方法,不可以直接创建,影刺构造器私有;
- 内置静态方法,返回对象;
- 为便于自定义统一结果的信息,建议使用链式编程,将返回对象设类本身,即return this;
- 响应数据由于为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方法收集到一个类型,并应用到所有控制器上
该类中的设计思路:
- 使用@ExceptionHandler注解捕获指定或自定义的异常;
- 使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
- 必须定义一个通用的异常捕获方法,便于捕获未定义的异常信息;
- 自定一个异常类,捕获针对项目或业务的异常;
- 异常的对象信息补充到统一结果枚举中;
自定义全局异常类
@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": {}
}