SpringBoot初体验-全局异常处理

258 阅读3分钟

前言

      异常处理是任何系统网站都无法忽略的,在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),一起探讨一起进步。