Spring MVC原始碼(四) ----- 統一異常處理原理解析

chen_hao發表於2019-07-18

SpringMVC除了對請求URL的路由處理特別方便外,還支援對異常的統一處理機制,可以對業務操作時丟擲的異常,unchecked異常以及狀態碼的異常進行統一處理。SpringMVC既提供簡單的配置類,也提供了細粒度的異常控制機制。

SpringMVC中所有的異常處理通過介面HandlerExceptionResolver來實現,介面中只定義了一個方法

public interface HandlerExceptionResolver {

    ModelAndView resolveException(
        HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}

方法中接受request和response資訊,以及當前的處理Handler,和丟擲的異常物件。並且提供抽象類AbstractHandlerExceptionResolver,實現resolveException方法,支援前置判斷和處理,將實際處理抽象出doResolveException方法由子類來實現。

@ControllerAdvice和@ExceptionHandler的簡單使用

@ControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler({ArrayIndexOutOfBoundsException.class})
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseDTO handleArrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException e) {
        // TODO 記錄log日誌
        e.printStackTrace();
        ResponseDTO responseDTO = new ResponseDTO();
        responseDTO.wrapResponse(ServiceCodeEnum.E999997, "陣列越界異常");

        return responseDTO;
    }

    @ExceptionHandler(value = ParamException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseDTO handleParamException(ParamException e) {
        // TODO 記錄log日誌
        e.printStackTrace();
        ResponseDTO responseDTO = new ResponseDTO();
        responseDTO.wrapResponse(ServiceCodeEnum.E999998, "輸入引數錯誤");

        return responseDTO;
    }
  
    @ExceptionHandler({Exception.class})
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseDTO handleException(Exception e) {
        // TODO 記錄log日誌
        e.printStackTrace();
        ResponseDTO responseDTO = new ResponseDTO();
        responseDTO.wrapResponse(ServiceCodeEnum.E999999, "未知異常");
        return responseDTO;
    }

}

我們看看 @ControllerAdvice

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] assignableTypes() default {};

    Class<? extends Annotation>[] annotations() default {};
}
ControllerAdvice 被 @Component 修飾,則說明標記 @ControllerAdvice 會被掃描到容器中

Spring mvc 的配置如下(這裡用到了mvc:annotation-driven):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
    xmlns:p="http://cxf.apache.org/policy" xmlns:ss="http://www.springframework.org/schema/security"
    xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
    http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd 
    http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd 
    http://cxf.apache.org/policy http://cxf.apache.org/schemas/policy.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
    http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd 
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd 
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan
        base-package="frame.web.controller;frame.web.advice" />

    <!--===================== view resovler ===================== -->
    <bean id="jstlViewResolver"
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="order" value="1" />
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
    </bean>

    <mvc:annotation-driven/>


    <!-- 自定義引數轉換 -->
    <bean id="conversionService"
        class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    </bean>

</beans>
AnnotationDrivenBeanDefinitionParser類就是用於解析<mvc:annotation-drive>標籤的。下面是AnnotationDrivenBeanDefinitionParser的部分原始碼:
package org.springframework.web.servlet.config;

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

    /**
    *parse是這個類的核心方法,它用於解析 annotation-drive標籤裡的內容,根據標籤裡的內容往spring ioc容器裡注入具體的物件。
    **/
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        Object source = parserContext.extractSource(element);
        XmlReaderContext readerContext = parserContext.getReaderContext();

        CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
        parserContext.pushContainingComponent(compDefinition);

        RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
        //這裡有我們熟悉的RequestMappingHandlerMapping,
        RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
        handlerMappingDef.setSource(source);
        handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        handlerMappingDef.getPropertyValues().add("order", 0);
        handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);

        if (element.hasAttribute("enable-matrix-variables")) {
            Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
        }
        else if (element.hasAttribute("enableMatrixVariables")) {
            Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
        }

        configurePathMatchingProperties(handlerMappingDef, element, parserContext);
        readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);

        RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
        handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);

        //這裡會注入具體的ConversionService用於將json,xml轉成Spring mvc裡的請求和返回物件
        RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);

        RuntimeBeanReference validator = getValidator(element, source, parserContext);
        RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);

        RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
        bindingDef.setSource(source);
        bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        bindingDef.getPropertyValues().add("conversionService", conversionService);
        bindingDef.getPropertyValues().add("validator", validator);
        bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

        ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
        ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);
        ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);
        String asyncTimeout = getAsyncTimeout(element);
        RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
        ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
        ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);
        //RequestMappingHandlerAdapter也會在這裡注入
        RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
        handlerAdapterDef.setSource(source);
        handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
        handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
        handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
        addRequestBodyAdvice(handlerAdapterDef);
        addResponseBodyAdvice(handlerAdapterDef);

        if (element.hasAttribute("ignore-default-model-on-redirect")) {
            Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
            handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
        }
        else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
            // "ignoreDefaultModelOnRedirect" spelling is deprecated
            Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
            handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
        }

        if (argumentResolvers != null) {
            handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
        }
        if (returnValueHandlers != null) {
            handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
        }
        if (asyncTimeout != null) {
            handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
        }
        if (asyncExecutor != null) {
            handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
        }

        handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
        handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
        readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef);

        String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
        RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
        uriCompContribDef.setSource(source);
        uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
        uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
        readerContext.getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef);

        RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
        csInterceptorDef.setSource(source);
        csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
        RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
        mappedCsInterceptorDef.setSource(source);
        mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
        mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
        String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedCsInterceptorDef);

        //這裡有我們需要找的ExceptionHandlerExceptionResolver,
        RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
        exceptionHandlerExceptionResolver.setSource(source);
        exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
        exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
        exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
        addResponseBodyAdvice(exceptionHandlerExceptionResolver);

        if (argumentResolvers != null) {
            exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
        }
        if (returnValueHandlers != null) {
            exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
        }

        String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver);

        RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
        responseStatusExceptionResolver.setSource(source);
        responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        responseStatusExceptionResolver.getPropertyValues().add("order", 1);
        String responseStatusExceptionResolverName =
                readerContext.registerWithGeneratedName(responseStatusExceptionResolver);

        RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
        defaultExceptionResolver.setSource(source);
        defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        defaultExceptionResolver.getPropertyValues().add("order", 2);
        String defaultExceptionResolverName =
                readerContext.registerWithGeneratedName(defaultExceptionResolver);

        parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
        parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
        parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
        parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
        parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));

        // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
        MvcNamespaceUtils.registerDefaultComponents(parserContext, source);

        parserContext.popAndRegisterContainingComponent();

        return null;
    }
}
通過上面程式碼的分析, 我們可以找到ExceptionHandlerExceptionResolver這個類來用於處理Spring MVC的各種異常,那ExceptionHandlerExceptionResolver具體又是如何跟ControllerAdvice配合使用來處理各種異常的呢?我們來看看ExceptionHandlerExceptionResolver裡的關鍵程式碼:
package org.springframework.web.servlet.mvc.method.annotation;

//我們考到這個類實現了InitializingBean,則容器初始化的時候在例項化此Bean後會呼叫afterPropertiesSet()
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
        implements ApplicationContextAware, InitializingBean {
    //這裡有個map用於儲存ControllerAdviceBean和ExceptionHandlerMethodResolver
    private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
            new LinkedHashMap<ControllerAdviceBean, ExceptionHandlerMethodResolver>();

    //這個方法是由spring 容器呼叫的
    @Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBodyAdvice beans
        //這個方法裡會處理ExceptionHandler
        initExceptionHandlerAdviceCache();

        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

    /**
    *這個方法裡會在spring ioc容器裡找出標註了@ControllerAdvice的類,如果有方法標註了@ExceptionHandler會生成一個ExceptionHandlerMethodResolver類用於處理異常並放到exceptionHandlerAdviceCache這個map快取類裡。
    **/
    private void initExceptionHandlerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for exception mappings: " + getApplicationContext());
        }
        //這裡會找到容器裡標註了@ControllerAdvice註解的類
        List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
        AnnotationAwareOrderComparator.sort(adviceBeans);

        for (ControllerAdviceBean adviceBean : adviceBeans) {
            //這個構造方法裡會檢查ControllerAdvice類裡是否有@ExceptionHandler標註的方法,在ExceptionHandlerMethodResolver 有個異常的map。
            //在ExceptionHandlerMethodResolver構造器中會通過反射拿到所有標註@ExceptionHandler的方法並加入ExceptionHandlerMethodResolver的map中
            //key為  @ExceptionHandler(value = ParamException.class) 標註的value,這裡就是ParamException.class,值為標註@ExceptionHandler的Method
            ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
            if (resolver.hasExceptionMappings()) {
                //如果有@ExceptionHandler方法,會執行下面的邏輯
                //將標註@ControllerAdvice的類Bean,和此Bean中封裝了所有Exception為key,Method為value的Map的ExceptionHandlerMethodResolver物件加入到exceptionHandlerAdviceCache的快取中
                this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
                if (logger.isInfoEnabled()) {
                    logger.info("Detected @ExceptionHandler methods in " + adviceBean);
                }
            }
            if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) {
                this.responseBodyAdvice.add(adviceBean);
                if (logger.isInfoEnabled()) {
                    logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
                }
            }
        }
    }

    /**
    ** 這個方法會根據exceptionHandlerAdviceCache這個找到具體需要處理異常的方法,這個後面再講
    */
    protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
        Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);

        if (handlerMethod != null) {
            ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
            if (resolver == null) {
                resolver = new ExceptionHandlerMethodResolver(handlerType);
                this.exceptionHandlerCache.put(handlerType, resolver);
            }
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
            }
        }

        for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
            if (entry.getKey().isApplicableToBeanType(handlerType)) {
                ExceptionHandlerMethodResolver resolver = entry.getValue();
                //根據具體的異常找到處理異常的方法,然後呼叫
                Method method = resolver.resolveMethod(exception);
                if (method != null) {
                    return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
                }
            }
        }

        return null;
    }
}

我們來看看 ExceptionHandlerMethodResolver這個類

public class ExceptionHandlerMethodResolver {
    public static final MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() {
        public boolean matches(Method method) {
            return AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null;
        }
    };
    private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis", new Class[0]);
    //此快取Map存放了@ControllerAdvice中所有註解了@ExceptionHandler的方法,其中@ExceptionHandler的value也就是Exception做為Key,值為當前Method
    private final Map<Class<? extends Throwable>, Method> mappedMethods = new ConcurrentHashMap(16);
    private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentHashMap(16);

    public ExceptionHandlerMethodResolver(Class<?> handlerType) {
        //通過反射拿到當前Class的所有方法,也就是標註了@ControllerAdvice的所有方法
        Iterator var2 = MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS).iterator();

        //遍歷所有的方法,尋找標註了@ExceptionHandler的方法
        while(var2.hasNext()) {
            Method method = (Method)var2.next();
            //這裡獲取到標註了@ExceptionHandler的方法上所有的@ExceptionHandler中的value
            //如{ArrayIndexOutOfBoundsException.class,ParamException.calss}
            Iterator var4 = this.detectExceptionMappings(method).iterator();

            while(var4.hasNext()) {
                Class<? extends Throwable> exceptionType = (Class)var4.next();
                //將ArrayIndexOutOfBoundsException.class作為key,method做為value加入到map快取中
                this.addExceptionMapping(exceptionType, method);
            }
        }

    }
    
    private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
        List<Class<? extends Throwable>> result = new ArrayList();
        //這裡獲取到標註了@ExceptionHandler的方法上所有的@ExceptionHandler中的value
        //如{ArrayIndexOutOfBoundsException.class,ParamException.calss}
        this.detectAnnotationExceptionMappings(method, result);
        if (result.isEmpty()) {
            Class[] var3 = method.getParameterTypes();
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Class<?> paramType = var3[var5];
                if (Throwable.class.isAssignableFrom(paramType)) {
                    result.add(paramType);
                }
            }
        }

        Assert.notEmpty(result, "No exception types mapped to {" + method + "}");
        return result;
    }
    
    protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
        //判斷此方法是否標記@ExceptionHandler,如果沒有則返回null,如果有標記則返回ExceptionHandler
        ExceptionHandler ann = (ExceptionHandler)AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
        //獲取ExceptionHandler註解的所有值,這裡是一個陣列,有可能有多個,如@ExceptionHandler({ArrayIndexOutOfBoundsException.class,ParamException.calss})
        result.addAll(Arrays.asList(ann.value()));
    }

}

異常處理原理

SpringMVC怎麼在請求處理的過程中完成對異常的統一處理的呢?我們從原始碼來深度解讀。

回到DispatcherServlet的doDispatcher方法

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());

    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;
}
catch (Throwable err) {
    dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

可以看到對請求處理的核心處理使用一個大的try/catch,如果出現異常,統一封裝成dispatchException交給processDispatchResult方法進行處理。我們知道processDispatchResult方法用來對返回檢視進行操作,而同時也對異常進行統一處理。

在processDispatchResult中,首先對異常進行判斷。

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);
    }
}

如果不是特殊的ModelAndViewDefiningException,則由processHandlerException來操作。

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;
}

我們主要關注異常處理器對異常的處理,SpringMVC通過HandlerExceptionResolver的resolveException呼叫實現類的實際實現方法doResolveException。

推薦部落格

  程式設計師寫程式碼之外,如何再賺一份工資?

ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver支援了@ExceptionHandler註解的實現。它的抽象基類AbstractHandlerMethodExceptionResolver繼承了AbstractHandlerExceptionResolver,doResolveException方法實際呼叫ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法。

protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    return this.doResolveHandlerMethodException(request, response, (HandlerMethod)handler, ex);
}

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {

    // 根據HandlerMethod和exception獲取異常處理的Method
    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
    if (exceptionHandlerMethod == null) {
        return null;
    }

    // 設定異常處理方法的引數解析器和返回值解析器
    exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();

    // 執行異常處理方法
    try {
        if (logger.isDebugEnabled()) {
            logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
        }
        Throwable cause = exception.getCause();
        if (cause != null) {
            // Expose cause as provided argument as well
            exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
        }
        else {
            // Otherwise, just the given exception as-is
            exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
        }
    }
    catch (Throwable invocationEx) {
        // Any other than the original exception is unintended here,
        // probably an accident (e.g. failed assertion or the like).
        if (invocationEx != exception && logger.isWarnEnabled()) {
            logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
        }
        // Continue with default processing of the original exception...
        return null;
    }

    // 對返回的檢視模型進行處理
    if (mavContainer.isRequestHandled()) {
        return new ModelAndView();
    }
    else {
        ModelMap model = mavContainer.getModel();
        HttpStatus status = mavContainer.getStatus();
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
        mav.setViewName(mavContainer.getViewName());
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            request = webRequest.getNativeRequest(HttpServletRequest.class);
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
        return mav;
    }
}

我們主要關注的是如何匹配到異常處理方法的,也就是 ExceptionHandlerExceptionResolver中的 getExceptionHandlerMethod

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
    Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);

    // 從當前Controller中匹配異常處理Method,此處我們暫時不分析
    if (handlerMethod != null) {
        ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
        if (resolver == null) {
            resolver = new ExceptionHandlerMethodResolver(handlerType);
            this.exceptionHandlerCache.put(handlerType, resolver);
        }
        Method method = resolver.resolveMethod(exception);
        if (method != null) {
            return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
        }
    }

    // 從ControllerAdvice中匹配異常處理Method
    // 我們知道容器初始化的時候,已經尋找所有標註了@ControllerAdvice的類,並將此類標註了@ExceptionHandler的方法放到當前類的exceptionHandlerAdviceCache中
    //遍歷所有的@ControllerAdvice生成的快取
    for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
        if (entry.getKey().isApplicableToBeanType(handlerType)) {
            //拿到ExceptionHandlerMethodResolver,這個物件裡包含了標註為@ExceptionHandler的Key為Excpthion,value為Method的快取Map
            ExceptionHandlerMethodResolver resolver = entry.getValue();
            //尋找ExceptionHandlerMethodResolver有沒有標註@ExceptionHandler能匹配當前異常的方法
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
            }
        }
    }

    return null;
}

public Method resolveMethod(Exception exception) {
    Method method = this.resolveMethodByExceptionType(exception.getClass());
    if (method == null) {
        Throwable cause = exception.getCause();
        if (cause != null) {
            method = this.resolveMethodByExceptionType(cause.getClass());
        }
    }

    return method;
}

public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
    //先從exceptionLookupCache快取中拿,第一次肯定拿不到,因為我們是存在mappedMethods這個快取中
    Method method = (Method)this.exceptionLookupCache.get(exceptionType);
    if (method == null) {
        method = this.getMappedMethod(exceptionType);
        this.exceptionLookupCache.put(exceptionType, method != null ? method : NO_METHOD_FOUND);
    }

    return method != NO_METHOD_FOUND ? method : null;
}

private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
    List<Class<? extends Throwable>> matches = new ArrayList();
    //拿到所有的Key
    Iterator var3 = this.mappedMethods.keySet().iterator();

    while(var3.hasNext()) {
        Class<? extends Throwable> mappedException = (Class)var3.next();
        //判斷exceptionType是不是mappedException本身或者其子類
        if (mappedException.isAssignableFrom(exceptionType)) {
            matches.add(mappedException);
        }
    }

    if (!matches.isEmpty()) {
        Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
        //返回匹配到異常的Method
        return (Method)this.mappedMethods.get(matches.get(0));
    } else {
        return null;
    }
}

匹配到exceptionHandlerMethod後,設定一些方法執行的環境,然後呼叫ServletInvocableHandlerMethod中的invokeAndHandle去執行,這個呼叫過程和正常請求的呼叫就是一致了。

  exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, new Object[]{exception, cause, handlerMethod});

這裡就是呼叫異常處理的方法,總體來說,就是SpringMvc啟動的時候初始化異常處理的元件,將 @ControllerAdvice標記的特殊類和@ExceptionHandler 標記的方法存入快取中,當目標Controller出現異常的時候,就通過丟擲的異常在快取中找到對應的處理方法,然後去呼叫對應的異常處理方法就OK了。

 

相關文章