在前后端分离项目中,系统中的异常不适合直接传递到前端,我们可以用错误码和异常信 息封装成的自定义异常类以json的形式发送到前端,前端再根据错误码指定的错误类型和错误信息对用户完成合适的提示,这篇文章就来具体说说怎么去实现这种异常处理方式
通用返回模型
为了方便后端向前端传送数据,我们得先实现一个适用所有场景的返回模型,这个模型包含请求的处理结果(成功与否)以及需要返回的数据模型
public class CommonReturnType {
// 表明对应请求的返回处理结果,success或fail
private String status;
// success返回对应VO的json数据,fail则返回通用的错误码格式
private Object data;
public static CommonReturnType create(Object result) {
return CommonReturnType.create(result, "success");
}
public static CommonReturnType create(Object result, String status) {
CommonReturnType type = new CommonReturnType();
type.setStatus(status);
type.setData(result);
return type;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
这个通用返回模型用在Controller中,整个Controller层都可以用这个模型向前端发送数据,然后前端可以解析json获取这个模型,根据响应状态和响应数据完成页面的构建
@RequestMapping("/test")
@ResponseBody
public CommonReturnType test() {
// 返回的json数据就是{status:"success", data:null}
return CommonReturnType.create(null);
}
业务异常模型
前面我们说到需要一个用错误码和异常信息封装而成的自定义异常类,那我们可以使用装饰器模式来达到实现业务异常类并且封装业务异常类型的目的
- 首先定义一个核心的错误接口,规范异常类的基本行为,可以获取错误码和错误信息以及覆盖原有的错误信息
public interface CommonError {
int getErrorCode();
String getErrorMsg();
CommonError setErrorMsg(String errorMsg);
}
- 然后我们实现一个继承了错误接口的枚举类来封装业务异常类型(包含错误码以及错误描述),这里只简单定义了两种通用错误,各个业务模块需要的异常类型也同样可以封装进这个枚举类以供使用,并且实现setErrorMsg方法使枚举对象能够用自定义信息覆盖原始错误信息
public enum EmBusinessError implements CommonError {
// 通用错误的错误码以及错误描述
UNKNOWN_ERROR(10001, "未知错误"),
PARAMETER_VALIDATION_ERROR(10002, "参数错误"),
;
private EmBusinessError(int errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
private int errorCode;
private String errorMsg;
@Override
public int getErrorCode() {
return this.errorCode;
}
@Override
public String getErrorMsg() {
return this.errorMsg;
}
@Override
public CommonError setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
return this;
}
}
- 最后我们实现业务异常类,需要实现错误接口并且继承Exception类,我们可以通过构造器直接把EmBusinessError内部的异常类型封装到业务异常类中
public class BusinessException extends Exception implements CommonError {
private CommonError commonError;
// 直接接收EmBusinessError用于构造业务异常
public BusinessException(CommonError commonError) {
super();
this.commonError = commonError;
}
// 同时接收EmBusinessError和自定义错误信息并覆盖掉原本的错误信息
public BusinessException(CommonError commonError, String errorMsg) {
super();
this.commonError = commonError;
this.commonError.setErrorMsg(errorMsg);
}
@Override
public int getErrorCode() {
return commonError.getErrorCode();
}
@Override
public String getErrorMsg() {
return commonError.getErrorMsg();
}
@Override
public CommonError setErrorMsg(String errorMsg) {
return commonError.setErrorMsg(errorMsg);
}
}
- 到这里就已经简单地实现了业务异常模型,现在我们可以用前面的通用返回模型来向前端发送异常信息了,而且我们需要一个只存储了异常对象中的错误码和错误信息的Map来作为通用返回模型的数据,这样才可以过滤一些无用的信息
@RequestMapping("/test")
@ResponseBody
public CommonReturnType test() {
BusinessException e = new BusinessException(EmBusinessError.UNKNOWN_ERROR);
Map<String, Object> data = new HashMap<>();
data.put("errorCode", e.getErrorCode());
data.put("errorMsg", e.getErrorMsg());
// 返回的json数据就是{status:"success", data:{errorCode:10001, errorMsg:"未知错误"}}
return CommonReturnType.create(data, "fail");
}
全局异常处理
虽然现在已经可以向前端发送系统的异常信息了,但是每次出现异常都需要这样去返回错误信息是真的麻烦,而且系统出现了其他异常还是会直接返回错误页面给前端,这样的结果显然是不合理的,如果可以把异常都抛出,然后实现一个全局异常处理,把异常都拦截下来,再根据不同类型的异常用通用返回模型给前端发送错误信息就好了
好消息是SpringBoot提供的@ControllerAdvice和@ExceptionHandler两个注解可以帮我们实现这种操作,@ExceptionHandler能实现统一处理指定的一类异常,@ControllerAdvice是一种增强的Controller,可以用于定义@ExceptionHandler方法,适用于所有@RequestMapping方法,先看一下代码实现
@ControllerAdvice
public class GlobalExceptionHandler {
// 统一处理所有的异常
@ExceptionHandler(Exception.class)
@ResponseBody
public CommonReturnType doError(Exception e) {
e.printStackTrace();
Map<String, Object> data = new HashMap<>();
if (e instanceof BusinessException) {
BusinessException businessException = (BusinessException)e;
data.put("errorCode", businessException.getErrorCode());
data.put("errorMsg", businessException.getErrorMsg());
} else if (e instanceof NoHandlerFoundException) {
data.put("errorCode", EmBusinessError.UNKNOWN_ERROR.getErrorCode());
data.put("errorMsg", "没有找到访问路径");
} else {
data.put("errorCode", EmBusinessError.UNKNOWN_ERROR.getErrorCode());
data.put("errorMsg",EmBusinessError.UNKNOWN_ERROR.getErrorMsg());
}
return CommonReturnType.create(data, "fail");
}
}
通过实现了一个全局异常处理类,我们不仅可以处理业务异常,还可以处理404状态的异常以及其他所有的异常,这样前端接收到的永远都是规范的错误信息,我们也就可以放心地在Controller中抛出异常了
@RequestMapping("/test", method = {RequestMethod.GET})
@ResponseBody
public CommonReturnType test(@RequestParam(name = "id") Integer id) throws BusinessException {
if (id == null) {
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
}
}