SpringBoot MVC(8)异常的处理

650 阅读2分钟

我们写的接口不可能永远不出错,万一发生异常,需要有相对应的处理机制。

异常处理查找

DispatcherServlet->doDispatch():
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    catch (Exception ex) {
        dispatchException = ex;
    }
    // 这里捕获的是Error异常
    catch (Throwable err) {
        dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
DispatcherServlet->processDispatchResult():
    // 这里的exception就是前面的dispatchException,出错的时候不会为空
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }
DispatcherServlet->processHandlerException():
    // 这里的handlerExceptionResolvers是在Servlet初始化的时候赋的值
    if (this.handlerExceptionResolvers != null) {
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            // DefaultErrorAttributes对request的属性赋值,返回为null
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    }

在返回视图之前判断了是否有异常,如果有异常的话,处理它靠的是servlet下的handlerExceptionResolvers变量,并最终会执行HandlerExceptionResolverComposite类下的resolveException()方法。

HandlerExceptionResolverComposite->resolveException():
    for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
        ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
        if (mav != null) {
            return mav;
        }
    }
AbstractHandlerExceptionResolver->resolveException():
    ModelAndView result = doResolveException(request, response, handler, ex);
    return result;
AbstractHandlerMethodExceptionResolver->doResolveException():
    return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
ExceptionHandlerExceptionResolver->doResolveHandlerMethodException():
    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
ExceptionHandlerExceptionResolver->getExceptionHandlerMethod():
    handlerType = handlerMethod.getBeanType();
    ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
    if (resolver == null) {
        // 查找本类是否有@ExceptionHandler注解的方法
        resolver = new ExceptionHandlerMethodResolver(handlerType);
        this.exceptionHandlerCache.put(handlerType, resolver);
    }
    // 该异常是否符合本类@ExceptionHandler所定义的异常
    Method method = resolver.resolveMethod(exception);
    if (method != null) {
        // 有的话就直接返回了
        return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
    }
    ......
    for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
        ControllerAdviceBean advice = entry.getKey();
        if (advice.isApplicableToBeanType(handlerType)) {
            ExceptionHandlerMethodResolver resolver = entry.getValue();
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
            }
        }
    }
    return null;

resolver.resolveMethod(exception)方法查看异常是否符合@ExceptionHandler所定义的异常,如果是的话,就返回该方法,如果有多个会选取最合适的一个,顺序是先查找本类的异常处理方法,然后再看其它类的,如果都没有符合的话,在springBoot中默认跳转/error接口

异常处理定义

@ControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Map<String, Object> ajaxError(Throwable error, HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("error", error.getMessage());
        map.put("result", "error");
        return map;
    }
}

启动阶段,会从容器中查找含有@ControllerAdvice注解的bean,然后遍历bean,找出含有@ExceptionHandler的方法,赋到exceptionHandlerAdviceCache变量

代码位于ExceptionHandlerExceptionResolver->afterPropertiesSet()

异常处理

ExceptionHandlerExceptionResolver->doResolveHandlerMethodException():
    // 这里的exceptionHandlerMethod包含了异常处理类和异常处理方法、重新定义了参数处理方法、返回处理方法
    // 处理参数,执行方法,处理返回值
    exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
    if (mavContainer.isRequestHandled()) {
        // 如果方法中含有@ResponseBody注解,返回空的modelAndView
        return new ModelAndView();
    }
    else {
        // 返回带参数的modelAndView
        ......
    }
DispatcherServlet->processDispatchResult():
    if (exMv != null) {
        // 这里对应的是@ResponseBody的情况,返回null
        if (exMv.isEmpty()) {
            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            return null;
        }
        // 否则返回视图
        if (!exMv.hasView()) {
            String defaultViewName = getDefaultViewName(request);
            if (defaultViewName != null) {
                exMv.setViewName(defaultViewName);
            }
        }
        return exMv;
    }
DispatcherServlet->processDispatchResult():
    if (mv != null && !mv.wasCleared()) {
        // 视图跳转或重定向
        render(mv, request, response);
    }

可见,如果发生了异常且被异常处理方法捕捉到,会执行异常处理方法并返回它的视图

总结

异常处理方法需要满足以下两个条件之一:

  • 类存在@ControllerAdvice注解(确保其被扫描成bean)且该类下存在含有@ExceptionHandler注解的方法
  • 当前执行方法所在的controller类存在含有@ExceptionHandler注解的方法