spring中定义多个HandlerExceptionResolver,异常会怎么处理

前言

我们知道,spring提供了几种方式来统一异常,这样我们就不需要在controller的每个方法中都写烦人的try-catch了。主要有以下几种:

  1. @ExceptionHandler 注解
  2. HandlerExceptionResolver 接口
  3. @ControllerAdvice 注解

这里就不一一展开说明了,今天主要讲一下,如果项目中定义了两个HandlerExceptionResolver接口的实现类,那异常会被哪个类处理呢?

源码分析

最近在查看项目异常处理代码的时候发现,项目中有两个HandlerExceptionResolver的实现类,突然想到异常处理后就不会有异常抛出了,那要两个处理类做什么?只好看一看spring的源码一探究竟。

springmvc的入口DispatcherServlet类:

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Error err) {
        triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

可以看到spring对在请求的处理前后加了很多的逻辑,不是本次主题就不细说了,重点关注下面的这行代码,在这里spring会针对controller抛出的异常(Exception)做处理。

// 处理controller的结果(包括异常)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

跟进去看一下processDispatchResult方法,依然还在DispatcherServlet类中:

/**
 * Handle the result of handler selection and handler invocation, which is
 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
 */
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

    boolean errorView = false;

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isDebugEnabled()) {
            logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                    "': assuming HandlerAdapter completed request handling");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

有一个专门处理异常的方法processHandlerException,继续看,还在DispatcherServlet中:

/**
 * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
 * @param request current HTTP request
 * @param response current HTTP response
 * @param handler the executed handler, or {@code null} if none chosen at the time of the exception
 * (for example, if multipart resolution failed)
 * @param ex the exception that got thrown during handler execution
 * @return a corresponding ModelAndView to forward to
 * @throws Exception if no error ModelAndView found
 */
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
        Object handler, Exception ex) throws Exception {

    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
        exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
        if (exMv != null) {
            break;
        }
    }
    if (exMv != null) {
        if (exMv.isEmpty()) {
            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            return null;
        }
        // We might still need view name translation for a plain error model...
        if (!exMv.hasView()) {
            exMv.setViewName(getDefaultViewName(request));
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
        }
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }

    throw ex;
}

这里来分析一下spring的处理逻辑,首先,spring容器会存在一个HandlerExceptionResolver的列表,里面保存了所有HandlerExceptionResolver的实现类(这个列表怎么产生后后面会讲到)。而spring对异常的处理逻辑非常简单,依次遍历列表,只要某个处理器能够处理异常(返回值不为null),那就忽略后面的处理器了。所以关键就变成这个列表的顺序是怎么确定的?

其实在spring容器(当然,这里是web容器)初始化的时候就会设置这个列表,方法调用链:HttpServletBean.init() -> FrameworkServlet.initServletBean() -> FrameworkServlet.initWebApplicationContext() -> DispatcherServlet.onRefresh() -> DispatcherServlet.initStrategies() -> DispatcherServlet.initHandlerExceptionResolvers(). 其中HttpServletBean继承了HttpServlet。来看一下initHandlerExceptionResolvers方法:

/**
 * Initialize the HandlerExceptionResolver used by this class.
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
 * we default to no exception resolver.
 */
private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;

    if (this.detectAllHandlerExceptionResolvers) {
        // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
            // We keep HandlerExceptionResolvers in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    else {
        try {
            HandlerExceptionResolver her =
                    context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, no HandlerExceptionResolver is fine too.
        }
    }

    // Ensure we have at least some HandlerExceptionResolvers, by registering
    // default HandlerExceptionResolvers if no other resolvers are found.
    if (this.handlerExceptionResolvers == null) {
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
        }
    }
}

这里的BeanFactoryUtils.beansOfTypeIncludingAncestors()可以视为从容器中取出所有的HandlerExceptionResolver实现类,然后spring会对结果进行排序,那排序的比较逻辑是什么呢?

private int doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider) {
    boolean p1 = (o1 instanceof PriorityOrdered);
    boolean p2 = (o2 instanceof PriorityOrdered);
    if (p1 && !p2) {
        return -1;
    }
    else if (p2 && !p1) {
        return 1;
    }

    // Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
    int i1 = getOrder(o1, sourceProvider);
    int i2 = getOrder(o2, sourceProvider);
    return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}

可以简单理解为Order靠前的bean最终在集合中也是靠前的。但是Order存在相同的可能性,正好项目中的两个bean就定义了同一个Order,这个时候谁在前呢?可以通过@AutoConfigureBefore来指定配置类的加载顺序,这个时候先加载的类当然就在前面啦。毕竟spring用了Collections.sort()来排序,而sort方法底层可以认为使用了归并排序(实际是Tim排序,可以认为是一种优化后的归并排序),是一种稳定性排序。

总结

其实项目中是没有必要定义两个异常处理器的,但是我们用到了一个框架,需要覆盖框架里的异常处理器,所以才会同时存在两个。如果也碰到了这种情况,可以使用Ordered接口和@AutoConfigureBefore注解来指定顺序

    原文作者:绝色天龙
    原文地址: https://www.jianshu.com/p/aaaea41c76db
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞