痛点
在业务开发过程中,异常遍地开花多姿多彩,且万万不可将这些Exception直接暴露给用户,还要理解前端同学处理各种各种异常的烦恼。
网络错误导致的异常、用户瞎操作导致的异常、coding时一不小心遗留的bug导致的异常...😵
那么如何能够更优雅地处理这些异常,将各种各样的异常封装好给前端的同学处理,再传递到用户侧呢?
实现
CommonError: Interface,通用异常接口EmBusinessError: Enum,实现CommonError,定义异常枚举类型BusinessException: 异常类,实现CommonError,继承Exception类CommonReturnType:统一格式返回类型BaseController:异常统一处理实现类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 类才能有效果
同时可以注意到UserController 和 BaseController 中的方法的返回值都是 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内容设置为自定义的异常对象
到这里,大概的框架雏形已经出来了,业务抛出自定义的异常,对异常统一处理,返回统一的数据类型 剩下的问题就是:如何定义异常类型的规范呢?
其中剩下三者 CommonError、EmBusinessError、BusinessException 关系比较绕,可见下图帮助理解
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);
}