全局异常处理的引入
springboot项目运行过程中,我们一般存在2种异常:
- 业务异常:系统设计而引起的业务异常。比如不同权限用户对文章的修改权限不足,而权限低的用户在保存时,后台会对用户权限进行校验,当校验不通过时,后台则会抛出相应的权限不足异常,阻止往下继续修改。
- 技术异常(代码异常):代码编写过程中,未考虑对象是否为空,对null对象直接使用相关方法从而导致异常。
在项目中,如果不对后台代码的异常进行捕捉,而直接抛到前端,那么则会产生whitelable Error Page页面,如下:
这种页面的生成会影响用户交互过程的体验。因此,我们需要通过全局异常捕捉,封装异常的信息,传递给前端,减少乃至避免whilelabel error page的产生。
实现过程
项目的业务异常通常分为很多种:1. 逻辑异常;2. 数据异常;3. 权限不足;4. 编码异常....针对这种情况,我们可以使用一个ErrorEnum枚举类来定义这些异常,并在后期使用。
错误码枚举
对于整个后端项目的异常,我们可以大致定义如下:
import lombok.Getter;
@Getter
public enum ErrorEnum {
/**
* 错误类型枚举
*/
SUCCESS("0", "成功"),
DATA_ERROR("AP0001", "数据异常"),
NO_PERMISSION("AP0002", "当前用户无权限"),
// ....
SYSTEM_ERROR("AP9999", "系统异常,请稍后重试");
/**
* 错误码
*/
private final String code;
/**
* 错误描述
*/
private final String desc;
ErrorEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
这里,我们定义了成功码0,系统异常(AP9999,编码鲁棒性不足导致),权限不足(AP0002)....
自定义异常处理类
我们需要定制化的异常处理类,用于封装后端到前端的异常信息。该异常类至少需要包含2个字段:错误码(errorCode)、错误信息(errorMsg)。此外,该类同时需要拓展RuntimeException,表示这是个异常类,并可以通过代码主动抛出。
@Getter
public class AppTransException extends RuntimeException{
private String errorCode;
private String msg;
public AppTransException(ErrorEnum e, String msg){
super(msg);
this.msg = msg;
this.errorCode = e.getCode();
}
public AppTransException(ErrorEnum e){
super(e.getDesc());
this.msg = e.getDesc();
this.errorCode = e.getCode();
}
}
异常处理逻辑
在定义完交易异常类之后,我们可以定义一个ExceptionHandler类,用于处理交易过程中产生的异常。在异常处理中,代码会判断异常是否为业务异常,为业务异常,则将业务的信息、错误码封装,若为其他异常,则抛系统异常。
该类存放于/core/config文件夹下。这里主要的是@ControllerAdvice和@ExceptionHandler两个注解,其中@ControllerAdvice是让springboot管理,表示这是个异常捕获类,而@ExceptionHandler中具体跟随的则是要捕捉的异常类型,该类型以及该类型的子类都能被该注解所对应的方法进行捕获。
// 开启全局异常捕捉
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionConfig {
/**
* 全局异常处理
* @param e
* @return
*/
@ExceptionHandler(RuntimeException.class)
public ResponseResult appExceptionHandle(Exception e){
ResponseResult result = new ResponseResult();
// 判断是否为业务异常(主动抛出)
if(e instanceof AppTransException){
appTransExceptionHandler(result, (AppTransException) e);
}else if(e instanceof BeanInstantiationException){
// 在新生成类时所使用
Throwable cause = ((BeanInstantiationException) e).getCause();
appTransExceptionHandler(result, (AppTransException) cause);
}else {
// 目前只有业务异常和其他异常
otherExceptionHandler(result, new AppTransException(ErrorEnum.SYSTEM_ERROR));
}
return result;
}
/**
* 业务异常时的处理
* @param result
* @param e
*/
private void appTransExceptionHandler(ResponseResult result, AppTransException e){
setErrorMessage(result, e.getMsg(), e.getErrorCode());
}
/**
* 目前其非业务异常均使用这个AP9999异常码
* @param result
* @param e
*/
private void otherExceptionHandler(ResponseResult result, AppTransException e){
setErrorMessage(result, e.getErrorCode(), e.getMsg());
}
private void setErrorMessage(ResponseResult result, String errorCode, String msg){
result.setErrorCode(errorCode);
result.setMsg(msg);
}
}
在代码中,业务异常,我们可以定制化错误信息,也可以使用预定义的错误信息。
使用效果
我们使用下面的代码就可以发起请求,查看全局异常处理的效果。
@PostMapping("/error1")
public ResponseResult error999(@RequestBody HelloDTO dto){
throw new AppTransException(ErrorEnum.SYSTEM_ERROR);
}