RuoYi-Vue 前后端分离版代码浅析-全局异常捕捉

2,384 阅读3分钟

这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战

前言

本节介绍RuoYi-Vue的模块中如何使用全局异常捕捉,全局异常捕捉对于每个项目而言都是必须的,在它的帮助下我们可以集中精力进行业务代码的开发,不用考虑异常的处理,所有的异常都可以由这里进行捕获之后统一处理。 ruoyi中使用的是@RestControllerAdvice,它相对@ControllerAdvice,减少了@ResponseBody注解,只需要在具体方法上添加@ExceptionHandler注解,对于我们现在前后端分离的代码而言,直接使用@RestControllerAdvice即可。

@RestControllerAdvice

一般来说我们的RestControllerAdvice必须的处理方法有以下几种

自定义验证异常

它主要是处理@Validated错误的,用来将返回的验证消息格式成我们已经写好的返回类。MethodArgumentNotValidExceptionBindException的子类。

/**
 * 自定义验证异常
 */
@ExceptionHandler(BindException.class)
public AjaxResult handleBindException(BindException e) {
    log.error(e.getMessage(), e);
    String message = e.getAllErrors().get(0).getDefaultMessage();
    return AjaxResult.error(message);
}

/**
 * 验证异常
 */
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    log.error(e.getMessage(), e);
    String message = e.getBindingResult().getFieldError().getDefaultMessage();
    return AjaxResult.error(message);
}

这里还有另外一个异常ConstraintViolationException也可以进行捕捉自定义验证异常,不过是在 javax.validation包中,而不是像上面两个异常在spring中,上面两个异常其实也不在一个包里。

业务异常

我们一般也会自定义一个业务异常,当逻辑上有问题时会使用这个异常向外抛出错误

/**
 * 业务异常
 */
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
{
    log.error(e.getMessage(), e);
    Integer code = e.getCode();
    return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}

系统异常

这一般是用来进行兜底的,如果其他的都拦不住的时候就要用系统异常来进行拦截了。

/**
 * 系统异常
 */
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e, HttpServletRequest request) {
    String requestURI = request.getRequestURI();
    log.error("请求地址'{}',发生系统异常.", requestURI, e);
    return AjaxResult.error(e.getMessage());
}

多个异常调用顺序

AbstractHandlerMethodExceptionResolver 是处理异常的抽象基础类,它是对HandlerExceptionResolver 这个接口的实现,继承了AbstractHandlerExceptionResolver

image.png 处理位置是

image.png 中的doResolveHandlerMethodException

image.png 在获取getExceptionHandlerMethod这里会根据错误类型来找对应能处理的方法 image.png

@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
   Method method = resolveMethodByExceptionType(exception.getClass());
   if (method == null) {
      Throwable cause = exception.getCause();
      if (cause != null) {
         method = resolveMethodByThrowable(cause);
      }
   }
   return method;
}

@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
   Method method = this.exceptionLookupCache.get(exceptionType);
   if (method == null) {
      method = getMappedMethod(exceptionType);
      this.exceptionLookupCache.put(exceptionType, method);
   }
   return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
}

这里的重点是用getMappedMethod来使用ExceptionDepthComparator这个比较器来找到最接近的比较器对这个Throwable来进行处理。

image.png

image.png 可以看到在getDepth这里是使用了一个递归来查深度的,

多个@RestControllerAdvice加载顺序

如果我们引用的别的api包中已经有了@RestControllerAdvice,每次都调用了它的处理,我们想改为自己的,可以使用@Order(Ordered.HIGHEST_PRECEDENCE)注解,将我们的处理类提升,但是一般来说不要这样做,可能会将别人的处理类覆盖掉,所以一定要做的话一定要将我们的处理的Exception写的越细越好,这样不会影响原有逻辑,防止处理了别人的逻辑。