前言
异常处理是任何系统网站都无法忽略的,在SpringBoot中,我们可以轻易实现统一的全局异常处理机制,统一捕获接口中任何地方抛出的异常信息。在一个完善的异常处理机制中,异常码是非常关键的,通过异常码,可以快速定位抛出异常的代码位置,方便前端对异常的处理和展示。
一、异常码枚举类
1、基础枚举父类,定义父类是为了规范各个模块异常枚举类的实现。
/**
* @author dengbh
* @date 2020-06-16
* 异常规范
* 每个模块可以自己扩展一些异常枚举
*/
public interface AbstractBaseExceptionEnum {
/**
* 获取异常的状态码
*/
Integer getCode();
/**
* 获取异常的提示信息
*/
String getMessage();
}
2、各个模块异常子类,每个模块最好给定某个异常码的范围,避免系统开发长久后异常码重复
/**
* 登录授权相关 1001-2000
*/
public enum UserAuthAccessExceptionEnum implements AbstractBaseExceptionEnum {
NOT_LOGIN_ERROR(1001, "用户未登录"),
USERNAME_ERROR(1002, "账号错误"),
LOGIN_EXPPIRED(1003, "登录已过期,请重新登录"),
USERNAME_EXIST(1004, "用户名已存在"),
INVALID_PHONE_NUM(1005, "无效的手机号");
private Integer code;
private String message;
UserAuthAccessExceptionEnum(int code, String message) {
this.code = code;
this.message = message;
}
@Override
public Integer getCode() {
return this.code;
}
@Override
public String getMessage() {
return this.message;
}
}
二、异常类封装
1、定义一个通用的业务异常类,继承RuntimeException
/**
* 业务异常的封装
*
* @author dengbh
*/
@Setter
@Getter
public class ServiceException extends RuntimeException {
private Integer code;
private String errorMessage;
private String errorCode;
public ServiceException(Integer code, String errorMessage) {
super(errorMessage);
this.code = code;
this.errorMessage = errorMessage;
}
public ServiceException(AbstractBaseExceptionEnum exception) {
super(exception.getMessage());
this.code = exception.getCode();
this.errorMessage = exception.getMessage();
}
/* 如果不需要打印异常堆栈信息,可以重写此方法
@Override
public synchronized Throwable fillInStackTrace() {
return null;
}*/
}
2、特殊的异常继承ServiceException,特殊异常之所以不是通过异常码区分,是为了在统一异常处理时方便返回不同的http响应码
/**
* 鉴权业务异常的封装
*
* @author dengbh
* @date 2020-06-17
*/
@Setter
@Getter
public class AuthorizedServiceException extends ServiceException {
public AuthorizedServiceException(Integer code, String errorMessage) {
super(code, errorMessage);
}
public AuthorizedServiceException(AbstractBaseExceptionEnum exception) {
super(exception);
}
}
三、全局异常拦截切面
在全局异常拦截切面中,会拦截所有的Controller中抛出的异常,对于主动抛出的业务异常,都是直接继续对外抛出即可。而对于被动抛出的异常,例如SQLException,会封装成我们的业务异常,并且给定特殊的异常码。
/**
* 监控所有controller
* 分类异常类
*/
@Component
@Slf4j
@Aspect
@Order(0)
public class ExceptionControllerAspect {
@Pointcut("execution (* top.haostudy..*Controller.*(..))")
public void excudeService() {
}
@Around("excudeService()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 包装异常类成为自定义类
try {
return joinPoint.proceed();
} catch (Throwable e) {
// 根据异常类型指定包装类
// 用户未登录异常
if (e instanceof ServiceException) {
// 业务异常无需处理
throw e;
} else if (e instanceof SQLException) {
// sql异常
throw new ServiceException(CommonExceptionEnum.SQL_ERROR);
}
// 未知异常
throw e;
}
}
}
四、全局异常拦截器
在全局的的异常拦截器中,通过注解@ExceptionHandler(ServiceException.class),我们可以指定需要拦截的异常,通过注解@ResponseStatus(HttpStatus.OK),指定Http响应码,通过注解@ResponseBody,标记我们返回JSON格式的数据。
对于自定义业务异常,我们把异常码,异常简述信息封装成对象后直接返回调用方。
而非自定义业务异常,会被定义为未知异常,赋予特定的异常码和信息后返回调用方。
/**
* 全局的的异常拦截器(拦截所有的控制器)
* (带有@RequestMapping注解的方法上都会拦截)
*
* @author dengbh
* @date 2020-06-16
**/
@ControllerAdvice
@Order(-1)
@Slf4j
public class GlobalExceptionHandler {
/**
* 拦截业务异常
*/
@ExceptionHandler(ServiceException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public ResponseData bussiness(ServiceException e) {
return new ResponseData(e.getCode(), e.getMessage(), e.getErrorCode());
}
/**
* 拦截鉴权异常
* 返回Http响应码401
*/
@ExceptionHandler(AuthorizedServiceException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ResponseBody
public ResponseData authorized(ServiceException e) {
return new ResponseData(e.getCode(), e.getMessage(), e.getErrorCode());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public ResponseData notFount(Exception e) {
log.error("运行时未知异常:", e);
return new ResponseData(CommonExceptionEnum.SERVER_ERROR.getCode(),
CommonExceptionEnum.SERVER_ERROR.getMessage(), null);
}
}
至此,一套简单通用的异常处理体系搭建完毕。
@各位掘金的小伙伴,如有任何Java技术方面疑问可私聊本人(wx:haostudydaydayup),一起探讨一起进步。