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

前言

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

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

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

源码分析

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

springmvc的入口DispatcherServlet类:

/**
 * Process the actual dispatching to the handler.
 * 

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. *

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.
 * 

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 matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerExceptionResolvers = new ArrayList(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方法底层可以认为使用了归并排序,是一种稳定性排序。

总结

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

本文由博客一文多发平台 OpenWrite 发布!

你可能感兴趣的:(java,spring)