1. 程式人生 > >SpringMVC原始碼分析

SpringMVC原始碼分析

1 Servlet功能分析

我們知道,WEB應用一般是打包成WAR包然後部署到Tomcat等應用伺服器上去的;部署完成後,通過URL訪問時,一般是http://ip:port/app這種訪問路徑。Tomcat根據路徑中埠後的第一個單詞並結合該應用的web.xml檔案,可以找到哪個應用的哪個Servlet需要處理該URL請求。
在Spring之前,如果要開發一個處理Http請求的功能,需要做兩項工作:一是開發繼承於Servlet類的業務處理Servlet;二是在Web.xml中註冊並對映該Servlet處理的請求路徑。
我們先來建立一個簡單原始的測試工程(通過Itellij測試),以瞭解原始Servlet的配置及生效過程,GITHUB地址:

https://github.com/icarusliu/learn

1.1 原始的WEB工程

第一步:配置Itellij Tomcat執行環境(Tomcat9):
這裡寫圖片描述
第二步:通過Itellij建立一個空白工程learn;
第三步:新增Module,名稱為testWebApp;
這裡寫圖片描述
Mvn初始化完成後的工程結構如下所示:
這裡寫圖片描述
第四步:構建工程,按下圖進行: 
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
第五步:執行Tomcat,將會跳轉到首頁;

第六步:定義測試Servlet
此處使用Kotlin,並引入log4j等日誌元件,具體的MVN配置檔案見GITHUB工程。
Servlet程式碼見下文,需要繼承自HttpServlet;

class TestServlet: HttpServlet() {
    val logger: Logger = LoggerFactory.getLogger(TestServlet::class.java)

    override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
        logger.info("Test servlet begin to execute!")

        response.outputStream.println("TestServlet"
) logger.info("Test servlet has been executed!") } }

該Servlet實現很簡單的向前臺返回TestServlet字串的過程;
此時Servlet定義即完成;但僅僅這樣還不夠,Tomcat並不知道什麼情況下來執行這個Servlet; 這個時候就需要在Web.xml檔案中進行Servlet的配置了,具體的配置見下文: 

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>test</servlet-name>
    <servlet-class>com.liuqi.learn.testWebApp.TestServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>test</servlet-name>
    <url-pattern>/test</url-pattern>
  </servlet-mapping>
</web-app>

以上配置主要包含兩個,一是Servlet的定義,名稱以及它後臺對應的Java類;二是Servlet與URL的對映關係,這裡表示將它對映到/test這個地址上。
這個時候我們啟動Tomcat,然後開啟連結:http://localhost:8080/test,就會看到瀏覽器輸出的了內容為TestServlet的頁面。
經過以上幾個步驟,一個最原始的不使用任何框架的WEB應用即構建完成。

經過以上分析,WEB應用一般就是一系列Servlet的集合;想象下,如果我們要完成A與B兩個功能,那我們就可以定義兩個Servlet並分別進行路徑對映; 但這樣的話功能越多Servlet就會越來越多,配置檔案也越來越複雜;如果在此基礎上進行抽象,Servlet只定義一個,在Servlet內部再根據URL進行功能分發,這樣就會簡單很多了。
SpringMVC實際就是這樣做的,它定義了一個名稱為DispatcherServlet的Servlet,用於接收請求並進行分發處理。

1.2 SpringMVC配置

上文已經分析了,SpringMVC的入口實際就是實現了一個名稱為DispatcherServlet的Servlet。
使用Spring而非Spring Boot的實現時,需要在Web.xml中配置該DispatcherServlet;

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

使用Spring Boot的載入過程將會在後續進行分析,此處暫不涉及。

1.3 Itellij查詢方法實現過程

在Java類中,可能出現A繼承B,B繼承C,C繼承D;D中有多個方法,有的在C中實現,有的在B,而有的在C;
像這種情況,在A中需要查詢某個方法的實現在其父類的哪個類中,可通過Ctrl+F12的快捷鍵來實現,其介面如下:
這裡寫圖片描述
如需要在DispatcherServlet中查詢Service的實現,我們按快捷鍵彈出查詢視窗後,輸入Service,會將名稱中包含Service的方法全部查找出來;如果選中了”Inherited members”,則其所有父類中符合條件的方法全部都會搜尋到,再滑鼠選中記錄後就可以方便的跳轉到需要查詢的方法實現的地方。
如上圖中表示Service在FrameworkServlet及HttpServlet中都有實現,通過檢視這兩個地方可以清楚的知道FrameworkServlet中的實現為最終實現。
這個操作在閱讀Spring原始碼時會經常使用到,後續不再說明。

2 SpringMVC分析

2.1 關鍵類分析

DispatcherServlet收到請求後,根據它本身所維護的URL路徑對映關係,決定呼叫哪個Controller來進行請求響應;其中涉及到的關鍵類見下表: 

說明
HandlerMapping 將請求對映到相應的處理器(Controller),在執行實際處理器前後會執行一系列的前置和後置攔截器;
HandlerAdapter 呼叫真正需要呼叫的處理器的處理方法;
ViewResolver 將Handler執行後返回的字串檢視名稱對映到實際的頁面或者其它的檢視上去;
WebApplicationContext 為Web應用提供配置功能;
ServletContext 提供了一系列方法以供Servlet與應用伺服器之間進行互動;每個應用每個虛擬機器啟動一個ServletContext物件; 因此不同Tomcat中的ServletContext是不能共享的;

我們先來看下幾個關鍵類在響應請求時是如何相互合作的:
這裡寫圖片描述
在後面的分析中我們將會看到這個請求響應過程是如何進行的。

2.2 生效過程原始碼分析

Servlet的關鍵方法為init與service;Init方法用於初始化ApplicationContext、BeanFactory等物件; 而Service用於響應請求。 我們將針對DispatcherServlet的init與service方法進行分析,以瞭解SpringMVC的初始化及請求響應過程。

2.2.1 初始化

我們先來看DispatcherServlt的Init方法。

2.2.1.1 init

經過原始碼閱讀,DispatcherServlet的init方法實際在ServletBean中定義,其實現如下:

@Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

實際呼叫initServletBean方法;該方法在DispatcherServlet的父類FrameworkServlet中定義: 

@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (this.logger.isInfoEnabled()) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                elapsedTime + " ms");
    }
}

其關鍵呼叫為initWebApplicationContext

2.2.1.2 initWebApplicationContext

該方法主要用於初始化WebApplicationContext,實現BeanFactory的初始化、Bean配置的載入等功能,其實現如下:

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        onRefresh(wac);
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }

    return wac;
}

實際就是對DispatcherServlet的webApplicationContext屬性進行配置與重新整理;
其關鍵過程就是如果屬性為空時,通過createWebApplicationContext建立屬性,此時例項化的實際類名為contextClass屬性所指定的類;該屬性的初始化是在哪個地方進行的?
跟蹤該屬性,我們可以看到在FrameworkServlet中有定義:

private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

實際DEFAULT_CONTEXT_CLASS的值為: XmlWebApplicationContext.class
也就是,如果沒有特殊配置,預設的ContextClass為XmlWebApplicationContext類;
該物件預設從名為/WEB-INF/applicationContext.xml的檔案中載入Bean定義;
其refresh方法定義如下:

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

其主要過程:
invokeBeanFactoryPostProcessors: 在Bean初始化前配置BeanFactory中的屬性,可修改Bean的定義屬性;
registerBeanPostProcessors:從容器中查詢BeanPostProcessors的Beans並進行註冊;
onRefresh: 初始化Servlet的一些屬性;
初始化完成後,如果呼叫ApplicationContext中的GetBean方法時,實際呼叫的是BeanFactory中的GetBean方法進行Bean的例項化等過程;關於Bean例項化的具體過程後續再進行分析。

2.2.1.3 onRefresh

我們來重點分析下DispatcherServlet的onRefresh方法,通過閱讀原始碼,可以發現其呼叫的實際為initStrategies方法,其實現如下:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

可以看到在這個地方會初始化HandlerMappings、HandlerAdapters、ViewResolvers等屬性;
我們先來看下HandlerMapping的初始化: 

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}

其執行過程為:如果配置了detectAllHandlerMappings屬性,則查詢所有型別為HandlerMapping的Bean;如果沒有配置則只查詢名稱為handlerMapping的Bean; 如果兩個都沒有查詢到則使用預設的HandlerMapping類。那預設的Handler在哪進行配置的?
跟蹤getDefaultStrategies方法,其邏輯就是從DispatcherServlet.properties檔案中查詢預設的配置項; 這個檔案的內容如下:

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

可以看到HandlerMapping如果使用者沒有進行配置的話,則使用預設的BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping兩個HandlerMapping。
HandlerAdapter等的初始化過程與HandlerMappings一致,不再贅述。

2.2.2 請求響應

Servlet的請求響應是通過Service方法來實現的;我們先來分析Service方法的實現;

2.2.2.1 Service方法實現分析

我們先來跟蹤Service方法的實現,在DispatcherServlet中已經沒有沒有Service方法定義,查詢其父類,查詢到最終實現在HttpServlet中,其關鍵程式碼段如下:

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    ...

    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);

    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);

    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);

    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);

    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);

    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //

        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

可見HttpServlet中實際呼叫的是doGet/doPost等方法;
再跟蹤doGet等方法的實現,以doGet為例,實際定義在FrameworkServlet中: 

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    processRequest(request, response);
}

實際呼叫為processRequest方法,其關鍵程式碼段如下:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    ...
        doService(request, response);
    ...
}

可以看到實際呼叫的是doService方法,而這個方法的實現就在DispatcherServlet中: 

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isDebugEnabled()) {
        String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
        logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
    }

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<String, Object>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

    try {
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}

該方法主要是為Request設定了一系列的屬性,最終呼叫doDispatch方法:

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;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", 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);
            }
        }
    }
}

此處看到,之前所介紹的幾個關鍵類已經開始起作用了;
該方法即為Service生效的關鍵過程,分析該方法執行流程如下: 
這裡寫圖片描述

接下來我們就上述流程中幾個關鍵的過程進行分析。

2.2.2.2 Handler查詢過程

handler查詢通過DispatcherServlet中的findHandler方法進行: 

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
            logger.trace(
                    "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
            return handler;
        }
    }
    return null;
}

其中,HandlerMappings這個屬性的初始化過程在2.2.1.3中已分析,我們知道預設情況下如果使用者未配置HandlerMapping類時使用預設的BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping,檢視DefaultAnnotationHandlerMapping類,看到這個類已經廢棄了,其建議是使用RequestMappingHandlerMapping類,而這個類是註解中主要使用的RequestMapping註解的處理類,因此此處我們主要分析RequestMappingHandlerMapping這個類的實現及生效過程。
我們從其getHandler方法開始分析;其getHandler方法在其父類的AbstractHandlerMapping中實現,其實現如下:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }

    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

這個方法主要做了兩個事情,一是呼叫getHandlerInternal獲取Handler;二是呼叫getHandlerExecutionChain獲取生效的各個攔截器並組裝成HandlerExecutionChain並返回;
跟蹤getHandlerInternal實現,其實現在類AbstractHandlerMethodMapping中,實際上呼叫了lookupHandlerMethod方法來獲取HandlerMethod:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<Match>();
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }

    if (!matches.isEmpty()) {
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        Collections.sort(matches, comparator);
        if (logger.isTraceEnabled()) {
            logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
                    lookupPath + "] : " + matches);
        }
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                        request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
            }
        }
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

其過程為先從mappingRegistry中獲取RequestMappingInfo物件,然後將所有的RequestMappingInfo物件轉換成Match物件;如果找到的Match物件列表不為空,則返回其第一個Match元素的HandlerMethod屬性;
這個地方邏輯有點繞;我們先來看下mappingRegistry屬性,這個屬性物件型別為MappingRegistry物件,主要包含了一系列的Map來儲存資料。 它的初始化過程在方法initHandlerMethods中進行: 

protected void initHandlerMethods() {
    if (logger.isDebugEnabled()) {
        logger.debug("Looking for request mappings in application context: " + getApplicationContext());
    }
    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
            getApplicationContext().getBeanNamesForType(Object.class));

    for (String beanName : beanNames) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            try {
                beanType = getApplicationContext().getType(beanName);
            }
            catch (Throwable ex) {
                // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                }
            }
            if (beanType != null && isHandler(beanType)) {
                detectHandlerMethods(beanName);
            }
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

實際上就是找出所有滿足isHandler方法的所有Bean,檢測其所有滿足條件的Methods,最終註冊到mappingRegistry中去。
isHandler的判斷依據,其實現在RequestMappingHandlerMapping中:  

protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

可以看到如果Bean被Controller或者是RequestMapping註解,則認為它是一個Handler。
而detectHandlerMethods方法完成的功能就是檢測滿足條件的Methods,並將其註冊到mappingRegistry屬性中去;

至此Handler的查詢過程已經分析完畢。

2.2.2.3 HandlerAdapter查詢過程

回到doDispatcher方法,在查詢Handler完成後即呼叫getHandlerAdapter(DispatcherServlet)來查詢HandlerAdapter: 

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    for (HandlerAdapter ha : this.handlerAdapters) {
        if (logger.isTraceEnabled()) {
            logger.trace("Testing handler adapter [" + ha + "]");
        }
        if (ha.supports(handler)) {
            return ha;
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

就是從handlerAdapters中查詢合適的HandlerAdapter並返回。在2.2.1.3中我們已經分析了handlerAdapters的載入過程,其載入的預設的HandlerAdapter為HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、AnnotationMethodHandlerAdapter
檢視AnnotationMethodHandlerAdapter發現其已建議被RequestMappingHandlerAdapter替換。

2.2.2.4 Handler執行過程

查詢到HandlerAdapter及Handler後就簡單了,最終就是通過HandlerAdapter呼叫到了Handler的handle方法;最終返回ModelAndView物件。
而前置及後置攔截器也就是在handle方法執行前後執行的。

相關推薦

JAVA springMVC原始碼分析

就簡單的寫一寫,面試可能會問,我一箇中級java開發,不會太多東西,大佬繞道! 建立一個Servlet工程,分三層寫好: … 2.建立web.xml <?xml version="1.0" encoding="UTF-8"?> <disp

看透SpringMVC原始碼分析與實踐(一)

一、網站架構及其演變過程   1.軟體的三大型別          軟體分為三個型別:單機軟體、BS結構的軟體(瀏覽器-服務端)、CS結構的軟體(客戶端-服務端)。 2.BS的基礎結構     &nb

看透SpringMVC原始碼分析與實踐(二)

一、Tomcat的頂層結構及啟動過程 1.Tomcat的頂層結構        Tomcat中最頂層的容器叫Server,代表整個伺服器,Server至少包含一個Service用於具體的服務。Service主要包含兩部分,Connector和Conta

springMVC原始碼分析[email protected

@SessionAttribute作用於處理器類上,用於在多個請求之間傳遞引數,類似於Session的Attribute,但不完全一樣,一般來說@SessionAttribute設定的引數只用於暫時的傳遞,而不是長期的儲存,長期儲存的資料還是要放到Session中。通過@Se

SpringMVC原始碼分析1:SpringMVC概述

轉載自:https://blog.csdn.net/a724888/article/details/76014532 Web MVC簡介 1.1、Web開發中的請求-響應模型: 在Web世界裡,具體步驟如下: 1、  Web瀏覽器(如IE)發起請求,如訪問http:/

springMVC原始碼分析--國際化LocaleResolver(一)

        springMVC給我們提供了國際化支援,簡單來說就是設定整個系統的執行語言,然後根據系統的執行語言來展示對應語言的頁面,一般我們稱之為多語言。springMVC國際化機制就是可以設定整個系統的執行語言,其定義了一個國際化支援介面LocaleResolver

SpringMVC原始碼分析--容器初始化(四)FrameworkServlet

一下SpringMVC配置檔案的地址contextConfigLocation的配置屬性,然後其呼叫的子類FrameworkServlet的initServletBean方法。 其實FrameworkServlet是springMVC初始化IOC容器的核心,通過讀取配置的c

springMVC原始碼分析--容器初始化(一)ContextLoaderListener

在spring Web中,需要初始化IOC容器,用於存放我們注入的各種物件。當tomcat啟動時首先會初始化一個web對應的IOC容器,用於初始化和注入各種我們在web執行過程中需要的物件。當tomcat啟動的時候是如何初始化IOC容器的,我們先看一下在web.xml中經常看

SpringMVC原始碼分析--容器初始化(五)DispatcherServlet

上一篇部落格SpringMVC原始碼分析--容器初始化(四)FrameworkServlet我們已經瞭解到了SpringMVC容器的初始化,SpringMVC對容器初始化後會進行一系列的其他屬性的初始化操作,在SpringMVC初始化完成之後會呼叫onRefresh(wac

springMVC原始碼分析--異常處理機制HandlerExceptionResolver執行原理(二)

上一篇部落格springMVC原始碼分析--異常處理機制HandlerExceptionResolver簡單示例(一)中我們簡單地實現了一個異常處理例項,接下來我們要介紹一下HandlerExceptionResolver是如何捕獲到Controller中丟擲的異常並展示到前

springMVC原始碼分析--異常處理機制HandlerExceptionResolver簡單示例(一)

springMVC對Controller執行過程中出現的異常提供了統一的處理機制,其實這種處理機制也簡單,只要丟擲的異常在DispatcherServlet中都會進行捕獲,這樣就可以統一的對異常進行處理。        springMVC提供了一個HandlerExcepti

springMVC原始碼分析--ControllerBeanNameHandlerMapping(八)

在上一篇部落格springMVC原始碼分析--AbstractControllerUrlHandlerMapping(六)中我們介紹到AbstractControllerUrlHandlerMapping定義了抽象方法buildUrlsForHandler,接下來我們看看在其

springMVC原始碼分析--攔截器HandlerExecutionChain(三)

上一篇部落格springMVC原始碼分析--HandlerInterceptor攔截器呼叫過程(二)中我們介紹了HandlerInterceptor的執行呼叫地方,最終HandlerInterceptor呼叫的地方是在HandlerExecutionChain中,接下來我們就

springMVC原始碼分析--ControllerClassNameHandlerMapping(九)

在上一篇部落格springMVC原始碼分析--AbstractControllerUrlHandlerMapping(六)中我們介紹到AbstractControllerUrlHandlerMapping定義了抽象方法buildUrlsForHandler,接下來我們看看在其

springMVC原始碼分析--動態樣式ThemeResolver(二)

ThemeResolver的體系結構如下:1、介面ThemeResolver中定義的介面是比較簡單的,提供兩個介面:(1)resolveThemeName獲取樣式名(2)setThemeName設定樣式名public interface ThemeResolver { /

springMVC原始碼分析--AbstractHandlerMethodMapping獲取url和HandlerMethod對應關係(十)

在之前的部落格 springMVC原始碼分析--AbstractHandlerMapping(二)中我們介紹了AbstractHandlerMethodMapping的父類AbstractHandlerMapping,其定義了抽象方法getHandlerInternal(Ht

springMVC原始碼分析--HandlerMethod

在之前的部落格中我們已經接觸過HandlerMethod,接下來我們簡單介紹一下HandlerMethod,簡單來說HandlerMethod包含的資訊包括類、方法和引數的一個資訊類,通過其兩個建構函式我們就可以瞭解其功能,對應著springMVC的Controller來說就

springMVC原始碼分析--HandlerInterceptor攔截器(一)

對SpringMVC有所瞭解的人肯定接觸過HandlerInterceptor攔截器,HandlerInterceptor介面給我們提供了3個方法:(1)preHandle: 在執行controller處理之前執行,返回值為boolean ,返回值為true時接著執行post

springMVC原始碼分析--頁面跳轉RedirectView(三)

跳轉的示例:@RequestMapping("/index") public String index(Model model,RedirectAttributes attr){ attr.addAttribute("attributeName", "attribute

springMVC原始碼分析--HandlerInterceptor攔截器呼叫過程(二)

在上一篇部落格springMVC原始碼分析--HandlerInterceptor攔截器(一)中我們介紹了HandlerInterceptor攔截器相關的內容,瞭解到了HandlerInterceptor提供的三個介面方法:(1)preHandle: 在執行controlle