1. 程式人生 > >Spring原始碼學習【八】SpringMVC之DispatcherServlet

Spring原始碼學習【八】SpringMVC之DispatcherServlet

目錄

一、前言

三、總結

一、前言

Web環境是Spring框架的重要應用場景,而SpringMVC又是Web開發中一個常用的框架,因此我們有必要學習一下SpringMVC的實現原理。

回到Web專案的配置檔案web.xml中,在使用SpringMVC時我們需要進行如下的配置:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/springMVC.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

熟悉Spring的同學對以上的配置肯定不陌生,這裡配置了一個DispatcherServlet,這個Servlet是由Spring實現的,是SpringMVC最核心的部分,如上配置的這個Servlet會接收所有的請求,最終將請求分發至對應的Controller進行處理,下面我們就從DsipatcherServlet入手,學習SpringMVC的實現。

二、原始碼學習

首先,來看一看DsipatcherServlet的類繼承關係(省略了部分介面):

從上圖中可以看到,DispatcherServlet間接繼承了HttpServlet,可用於處理Http請求。

既然DispatcherServlet也是Servlet家族中的一員,那麼它肯定要遵循Servlet的生命週期,即:

  • 初始化階段,呼叫init()方法 
  • 響應客戶請求階段,呼叫service()方法 
  • 銷燬階段,呼叫destroy()方法

有了這些瞭解,我們就可以順著DispatcherServlet的生命週期來學習SpringMVC的實現了。

(一)初始化階段 -> init()

首先,定位到初始化階段,在這個階段會呼叫init()方法,這個方法定義在Serlvet介面中,我們可以發現這個方法的最終實現在DispatcherServlet的父類HttpServletBean中,這個方法被定義為final方法,不可被子類覆蓋,程式碼如下:

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {

    @Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }
        // 獲取配置的 init parameters,設定Bean的屬性
        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;
            }
        }
        // 這個方法是一個模板方法,預設實現為空,交由其子類FrameworkServlet實現
        initServletBean();
        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

}

在上面的程式碼中,首先獲取了init parameters,也就是web.xml中的<init-param/>節點,並將init parameters設定為這個Servlet Bean的屬性,然後呼叫了子類FrameworkServlet的initServletBean()方法,進行額外的初始化處理,FrameworkServlet程式碼如下:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

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

    protected WebApplicationContext initWebApplicationContext() {
        // 首先從ServletContext中取得根應用上下文,也就是上一篇中在ContextLoader中建立的IOC容器
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
        if (this.webApplicationContext != null) {
            // 若在構造Servlet時已經注入應用上下文,則直接使用
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        // 設定根應用上下文
                        cwac.setParent(rootContext);
                    }
                    // 配置並重新整理應用上下文
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // 構造Servlet時未注入應用上下文,則到ServletContext中獲取
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // ServletContext中未獲取到,則建立一個應用上下文
            // 這裡建立應用上下文的處理與上一篇中的處理類似,不同之處在於建立完成後即進行了應用上下文的配置和重新整理
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            // 觸發初始化重新整理,這裡指的不是應用上下文的重新整理
            // 這個方法是一個模板方法,預設實現為空,交由子類DisispatcherServlet實現
            onRefresh(wac);
        }

        if (this.publishContext) {
            // 將當前的應用上下文釋出到ServletContext中,key為:FrameworkServlet.class.getName() + ".CONTEXT." + servletName
            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;
    }

}

上面的程式碼中,取得了一個應用上下文,作為了根IOC容器的子容器,這樣,DispatcherServlet中的IOC容器就建立起來了,細心的同學會發現,在返回應用上下文之前呼叫了onRefresh(wac)方法,這個方法由其子類DispatcherServlet實現,用於初始化Web層需要的策略,下面讓我們一起來看一看這部分的原始碼:

public class DispatcherServlet extends FrameworkServlet {

    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

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

}

從上面的程式碼中可以看到,在獲取應用上下文的過程中初始化了DispatcherServlet中需要的各種解析器,其中包括檔案解析器、區域解析器、主題解析器等。

解析器的初始化過程大體相同,都是從應用上下文中取得相應的Bean,若不存在則使用預設解析器策略。

具體關於各解析器的介紹大家可以參考一篇部落格:SpringMVC解析器

到這裡,DispatcherServlet的初始化階段就完成了,在這個過程中,一方面建立了DispatcherServlet的IOC容器,並將這個IOC容器作為根IOC容器的子容器,另一方面,初始化了DispatcherServlet需要的各種解析策略,接下來,DispatcherServlet將會在處理HTTP請求時發揮重要的作用。

(二)響應客戶請求 -> service()

我們知道Servlet在接收到客戶請求後會呼叫service()方法,根據請求型別執行doGet、doPost等一系列方法,在DispatcherServlet的繼承體系中,由DispatcherServlet的父類FrameworkServlet重寫了HttpServlet中的service()方法以及doGet()、doPost() 等一系列方法,下面以常用的HTTP請求方法來看一看FrameworkServlet的主要實現程式碼:

  • public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    
        @Override
        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
            // 這裡添加了對patch請求的支援
            if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
                processRequest(request, response);
            }
            else {
                // 這裡呼叫HttpServlet的service方法,根據請求型別呼叫該類中重寫的doGet、doPost等方法 
                super.service(request, response);
            }
        }
    
        @Override
        protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            processRequest(request, response);
        }
    
        @Override
        protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            processRequest(request, response);
        }
    
        @Override
        protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            processRequest(request, response);
        }
    
        @Override
        protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            processRequest(request, response);
        }
    
    }

從上面的程式碼中可以看到, DispatcherServlet接收到使用者請求後,會呼叫父類FrameworkServlet中的service()方法,最終根據請求型別呼叫FrameworkServlet中重寫的doGet、doPost等方法,這些方法都呼叫了processRequest()方法,下面讓我們看一下processRequest()的具體實現:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        // 獲取LocaleContext(語言環境), 用作備份
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        // 根據當前request建立LocaleContext
        LocaleContext localeContext = buildLocaleContext(request);
        // 獲取RequestAttributes,用作備份
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        // 根據當前request、response建立ServletRequestAttributes
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
        // 為當前請求註冊一個攔截器,用於在請求執行前後非同步初始化和重置FrameworkServlet的LocaleContextHolder和RequestContextHolder
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        // 根據當前request初始化ContextHolders
        initContextHolders(request, localeContext, requestAttributes);
        try {
            // 具體處理請求,是一個模板方法,由子類DispatcherServlet實現
            doService(request, response);
        }
        catch (ServletException | IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }
        finally {
            // 使用備份重置ContextHolders
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            if (logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", failureCause);
                }
                else {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        logger.debug("Leaving response open for concurrent processing");
                    }
                    else {
                        this.logger.debug("Successfully completed request");
                    }
                }
            }
            // 向該應用註冊的所有監聽器釋出RequestHandledEvent事件
            // 監聽器可以通過實現ApplicationListener介面來實現
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }

}

在上面的程式碼中我們可以看到一個用於處理請求的核心方法:doService(request, response),這個方法是一個模板方法,由其子類DispatcherServlet實現,程式碼如下:

public class DispatcherServlet extends FrameworkServlet {

    @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) + "]");
        }

        // 對於include請求,首先儲存request屬性快照,用於請求後恢復屬性
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            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));
                }
            }
        }

        // 為request設定一些必要的屬性
        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()); // 主題源,預設將應用上下文作為主題源

        if (this.flashMapManager != null) {
            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) {
                    // 恢復include請求屬性
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }

}

doService方法比較簡單,主要是為request設定一些必要的屬性,接下來呼叫了doDispatch方法進行請求的分發,這是SpringMVC中的核心功能,doDispatch方法中主要進行了如下的處理:

  • 處理攔截
  • 處理請求
  • 解析View

程式碼如下:

public class DispatcherServlet extends FrameworkServlet {

    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 {
                // 判斷是否為檔案上傳請求,這裡會嘗試將request轉換為檔案上傳請求
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // 通過HandlerMapping獲取請求匹配的處理器:一個HandlerExecutionChain類的例項
                // 這個處理器中包含一個請求處理器和多個攔截器
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    // 未找到匹配的處理器,返回404錯誤
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 根據匹配的請求處理器獲取支援該處理器的介面卡
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                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);
                    }
                    // 對於get請求,如果從上次修改後未進行修改則不再對請求進行處理
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 執行攔截器 -> preHandle
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 執行處理器處理請求,返回ModelAndView物件
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                // 若ModelAndView無檢視名,則為其設定預設檢視名
                applyDefaultViewName(processedRequest, mv);
                // 執行攔截器 -> postHandle
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 處理異常,解析View
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            // 觸發攔截器的afterCompletion方法
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            // 觸發攔截器的afterCompletion方法
            triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // 清除檔案上傳請求使用的資源
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

}

到這裡,客戶的請求就相應完成了。在這個過程中,首先處理匹配的處理器中的攔截器,然後通過處理器處理客戶的請求,最後通過檢視解析器解析和渲染檢視,這裡還有許多細節未深入分析,我們將在後續繼續學習。

(二)銷燬階段 -> destroy()

Servlet的銷燬階段會呼叫destroy()方法,這個方法的實現在FrameworkServlet中,實現比較簡單,就是將Servlet中的IOC容器關閉,程式碼如下:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

    @Override
    public void destroy() {
        getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
        // Only call close() on WebApplicationContext if locally managed...
        if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) {
            ((ConfigurableApplicationContext) this.webApplicationContext).close();
        }
    }
}

三、總結

本篇中,順著Servlet的生命週期大致分析了SpringMVC的核心類DispatcherServlet的實現,對SpringMVC的請求控制有了一定的瞭解,但在DispatcherServlet處理客戶請求的部分有許多內容未深入分析,需要進一步學習。