【SpringBoot】业务异常统一处理框架

647 阅读4分钟

痛点

在业务开发过程中,异常遍地开花多姿多彩,且万万不可将这些Exception直接暴露给用户,还要理解前端同学处理各种各种异常的烦恼。

网络错误导致的异常、用户瞎操作导致的异常、coding时一不小心遗留的bug导致的异常...😵

那么如何能够更优雅地处理这些异常,将各种各样的异常封装好给前端的同学处理,再传递到用户侧呢?

实现

image.png

  1. CommonError : Interface,通用异常接口
  2. EmBusinessError : Enum,实现CommonError,定义异常枚举类型
  3. BusinessException : 异常类,实现CommonError,继承Exception类
  4. CommonReturnType :统一格式返回类型
  5. BaseController :异常统一处理实现类
  6. UserController : 业务逻辑处理类(测试类)

这波先上测试类👇

@RequestMapping("/get")
@ResponseBody
public CommonReturnType getUser(@RequestParam(name="id")Integer id) throws BusinessException {
    UserModel userModel = userService.getUserById(id);

    if (userModel == null) {
        throw new BusinessException(EmBusinessError.USER_NOT_EXIST);
    }
    
    return CommonReturnType.create(userModel);
}

这是一个getUser信息的业务逻辑,当User信息为空时,会抛出一个BusinessException异常,因为该异常定义了一个构造方法的形参类型为 CommonError,所以入口参数可以放EmBusinessError


这个时候向系统抛出了一个异常,在Spring框架中,@ExceptionHandler注解可以获取系统抛出的异常并做处理

此时已经到了BaseController异常统一处理实现类中👇

@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handlerException(HttpServletRequest request, Exception ex) {
    Map<String, Object> responseData = new HashMap<>();
    if (ex instanceof BusinessException) {
        BusinessException exception = (BusinessException) ex;
        responseData.put("errCode", exception.getErrCode());
        responseData.put("errMsg", exception.getErrMsg());
    } else {
        responseData.put("errCode", EmBusinessError.UNKNOWN_ERROR.getErrCode());
        responseData.put("errMsg", EmBusinessError.UNKNOWN_ERROR.getErrMsg());
    }

    return CommonReturnType.create(responseData, "fail");
}

在这里对异常做统一处理(先别管异常的格式啥的,先看怎么用)

思路就是抛出自定义的异常,然后在定义的处理器里面进行处理

因为要做业务和异常的解耦,所以放到2个不同的类里面去实现

所以 UserController 类需要继承 BaseController 类才能有效果


同时可以注意到UserControllerBaseController 中的方法的返回值都是 return CommonReturnType.create(xxx);

CommonReturnType 类就是统一Controller层返回格式的实现类👇

@Data
public class CommonReturnType {
    private String status;
    private Object data;

    public static CommonReturnType create(Object data) {
        return CommonReturnType.create(data, "success");
    }

    public static CommonReturnType create(Object data, String status) {
        CommonReturnType type = new CommonReturnType();
        type.setData(data);
        type.setStatus(status);
        return type;
    }
}
  • status:取值"success" / "fail",假如抛出异常时,status为fail
  • data: 当status为success时,data内容设置为客户获取的数据(本例中为User对象),当status为fail时,data内容设置为自定义的异常对象

到这里,大概的框架雏形已经出来了,业务抛出自定义的异常,对异常统一处理,返回统一的数据类型 剩下的问题就是:如何定义异常类型的规范呢?


其中剩下三者 CommonErrorEmBusinessErrorBusinessException 关系比较绕,可见下图帮助理解

image.png

CommonError接口定义了3个方法,其中setErrMsg最为关键,它的存在可以使errMsg变得更加灵活

public interface CommonError {
    public int getErrCode();
    public String getErrMsg();
    public CommonError setErrMsg(String errMsg);
}

枚举类型 EmBusinessError 通过构造方法,定义异常类型的规范

  • PARAMETER_VALIDATION_ERROR(10001, "参数不合法")
  • UNKNOWN_ERROR(10002, "未知错误")
  • USER_NOT_EXIST(20001, "用户不存在")

新增异常类型需要在这个枚举中添加,所有抛出的自定义异常都必须时该枚举中的某一元素

public enum EmBusinessError implements CommonError{
    //通用错误类型
    PARAMETER_VALIDATION_ERROR(10001, "参数不合法"),
    UNKNOWN_ERROR(10002, "未知错误"),

    //20000开头为用户信息相关错误定义
    USER_NOT_EXIST(20001, "用户不存在"),
    ;

    private int errCode;
    private String errMsg;

    private EmBusinessError(int errCode, String errMsg) {
        this.errCode = errCode;
        this.errMsg = errMsg;
    }

    @Override
    public int getErrCode() {
        return this.errCode;
    }

    @Override
    public String getErrMsg() {
        return this.errMsg;
    }

    @Override
    public CommonError setErrMsg(String errMsg) {
        this.errMsg = errMsg;
        return this;
    }
}

可以回看前面的代码实现,都是取枚举中已定义的元素

//`UserController`中抛出异常的代码
if (userModel == null) {
    throw new BusinessException(EmBusinessError.USER_NOT_EXIST);
}
    
//BaseController 中处理异常的代码
if (ex instanceof BusinessException) {
    BusinessException exception = (BusinessException) ex;
    responseData.put("errCode", exception.getErrCode());
    responseData.put("errMsg", exception.getErrMsg());
} else {
    responseData.put("errCode", EmBusinessError.UNKNOWN_ERROR.getErrCode());
    responseData.put("errMsg", EmBusinessError.UNKNOWN_ERROR.getErrMsg());
}

BusinessException 作为一个业务异常实现类,继承Exception

通过 throw new BusinessException(xxx) 抛出异常对象,有2个构造器,可以使用默认errMsg或者自定义errMsg

此处生成的errMsg最终会在 BaseController 获取,封装成统一的CommoonReturnType返回到前端

public class BusinessException extends Exception implements CommonError{
    private CommonError commonError;

    //直接接收EmBusinessError的传参用于构造业务异常
    public BusinessException(CommonError commonError) {
        super();
        this.commonError = commonError;
    }

    //接收自定义errMsg的方式构造业务异常
    public BusinessException(CommonError commonError, String errMsg) {
        super();
        this.commonError = commonError;
        this.commonError.setErrMsg(errMsg);
    }

    @Override
    public int getErrCode() {
        return this.commonError.getErrCode();
    }

    @Override
    public String getErrMsg() {
        return this.commonError.getErrMsg();
    }

    @Override
    public CommonError setErrMsg(String errMsg) {
        this.commonError.setErrMsg(errMsg);
        return this;
    }
}

应用

上述框架搭好后,如果需要添加新的业务异常信息,只需一步即可

在枚举 EmBusinessError 中添加定义

USER_LOGIN_FAIL(20002, "用户名或密码错误")

随后业务处理中即可使用

if (StringUtils.isEmpty(telPhone) || StringUtils.isEmpty(password)) {
    throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL);
}