美文网首页Java服务端面试spring
spring中定义多个HandlerExceptionResol

spring中定义多个HandlerExceptionResol

作者: 绝色天龙 | 来源:发表于2019-04-22 20:31 被阅读40次

    前言

    我们知道,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注解来指定顺序

    相关文章

      网友评论

        本文标题:spring中定义多个HandlerExceptionResol

        本文链接:https://www.haomeiwen.com/subject/vyzugqtx.html