debug方式深入springMVC源碼的實現

前言

轉載請注明來源
? ? ? ? 上一次寫博客已經是5月份的事了,其實是蠻想抽時間寫寫,沉淀一下自己,無奈,天天加班加點,人都傻了,心累.真心覺得,對于一個搞技術的人來說,不能只是工作工作,雖然這是必要的(畢竟大多數人還是要吃飯的),但是經常的周期性的沉淀下自己,總結自己一段時間的收獲是分重要的.
? ? ? ? 話不多說了,本篇主要是針對一下springMVC的處理流程及實現原理通過debug方式層層深入理解.
? ? ? ? 整個篇幅過長,偏向于個人的一個debug過程的展示,以及個人理解的筆記,感興趣的可以參照我的流程進行debug,加上一些我個人的理解與提示,多debug幾遍流程,相信看的人都能得到自己的理解.

服務器原理簡介

springMVC作為現在Java這塊非常流行的MVC框架,基本上只要會spring就可以無縫銜接springMVC,
那么springMVC到底是如何工作的呢,這里不得不提的是服務器,相信在Java Web這塊的程序員們都非常的熟悉.
正好最近擼了點tomcat源碼的皮毛,了解了類tomcat等的web服務器的工作原理,有興趣的可以吃一吃我的安利
<<how tomcat works>> 這本書.
那么為什么需要服務器呢?
簡單來講,服務器通過ServerSocket獲取到Http請求然后對其解析并封裝成Request和Response對象,
然后將其交給Servlet容器進行處理,選擇對應的Servlet處理請求,返回結果(實際上是很復雜,作為一個web
程序員,這個真的是應該好好了解研究的).
那么為什么tomcat和springmvc可以結合起來呢,最最核心的原因是他們都是基于Servlet規范的,
由于Servlet規范,他們可以互相通信(服務器和SpringMVC的結合在debug時將會簡單體現).

SpringMVC

開始詳解SpringMVC了.

1、web.xml

 web.xml中配置了最重要的ContextLoaderListener以及DispatchServlet.
 ContextLoaderListener用于啟動web容器時,自動裝ApplicationContext的配置信息,
 由于 ContextLoaderListener實現了ServletContextListener,所以在web容器啟動應用時,
 創建ServletContext對象,每個應用都有一個對應的ServletContext對象,ServletContext在應用關閉
 時將會銷毀,在啟動時,可以向ServletContext中添加WebApplicationContext,這個在ServletContext
 整個運行期間都是可見的.
 DispatchServlet是SpringMVC最重要的一個類,它實現了Servlet,用于對請求做邏輯處理,相應的
 ContextLoaderListener實際上只是為了創建WebApplicationContext,DispatchServlet則是負責了
 SpringMVC中對客戶端請求的邏輯處理,我們的每個請求都是經過DispatchServlet進行處理,調用對應的
 邏輯實現(Controller中對應的請求的方法),返回視圖,所以說DispatchServlet是SpringMVC中最重要的一個
 類一點不為過.

2、啟動

終于要接觸代碼了,下面就對springMVC啟動過程的流程與邏輯進行debug。
本文采用的是Jetty服務器.

如下所示,web.xml中配置的監聽器類:

image.png

可以看到,該類實現了ServletContextListener,ServletContextListener接口,當系統啟動時,將會調用ServletContextListener的實現類的contextInitialized方法,用于在初始化時加入一些屬性,這樣就可以在全局范圍內調用這些屬性.
debug啟動web工程,直入主題,在contextInitialized方法出打斷點:


image.png

繼續跳入父類ContextLoader中的initWebApplicationContext方法中進行WebApplicationContext的創建,WebApplicationContext是繼承自ApplicationContext,在ApplicationContext的基礎上增加了一些特定于Web的操作及屬性,詳細可以自行查看.
下面是整個initWebApplicationContext方法的詳細代碼,加入了一點個人理解的注釋:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //驗證context是否已經存在,
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException(
                    "Cannot initialize context because there is already a root application context present - " +
                    "check whether you have multiple ContextLoader* definitions in your web.xml!");
        }

        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            //初始化 WebApplicationContext
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                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 ->
                        // determine parent for root web application context, if any.
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    //刷新上下文環境
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            //將WebApplicationContext記錄在servletContext中
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                //映射當前的類加載器與context實例到全局變量currentContextPerThread中
                currentContextPerThread.put(ccl, this.context);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }

            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }

debug到下圖280行,創建WebApplicationContext,

image.png

debug 跳入createWebApplicationContext(servletContext)方法中,

image.png

determineContextClass方法返回一個WebApplicationContext 接口的實現類,否則默認返回XmlWebApplicationContext 或者一個指定的context

image.png

此處有一個defaultStrategies,可以看下圖,ContextLoader有一個static代碼塊,


image.png
image.png

image.png

通過以上我們可以得知,在ContextLoader類加載的時候就先讀取了ContextLoader同級目錄下的ContextLoader.properties配置文件,在初始化WebApplicationContext時,根據其中的配置提取WebApplicationContext接口的實現類,并根據這個實現類通過反射的方式進行實例的創建.

image.png

接著debug走,將WebApplicationContext記錄在servletContext中


image.png

映射當前的類加載器與context實例到全局變量currentContextPerThread中


image.png
初始化servlet
   SpringMVC通過DispatcherServlet對請求進行處理,而DispatcherServlet是實現了Servlet接口的,而servlet接口是基于servlet規范編寫的一個Java類,實際上一個servlet會經歷三個階段:初始化階段、運行階段、銷毀階段,也就是我們熟知的servlet 的init、doGet/doPost、destroy這三個方法的執行,而dispatchservlet是實現了servlet接口的,那么必然也會經歷這三個階段,下面是DispatchServlet類的父類結構圖:
image.png

可以看到dispatServlet的父類FrameworkServlet,FrameworkServlet又繼承了HttpServletBean,實際上整個servlet的三個階段都在這三個類中做了具體的實現。
初始化階段在HttpServletBean中可以找到相應的方法,

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

        // Set bean properties from init parameters.
        try {
            //解析init parameters 并封裝到PropertyValues中
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            //將當前這個Servlet類轉化為BeanWrapper,從而能以spring的方式對init parameters的值進行注入
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            //注冊自定義屬性編輯器,一旦遇到resource類型的屬性將會使用ResourceEditor進行解析
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            //空實現,留給子類覆蓋
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

        // Let subclasses do whatever initialization they like.
        //在FrameworkServlet中覆蓋了該方法
        initServletBean();

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

debug進入init方法中,跳入FrameworkServlet的initServletBean方法中,如下圖

image.png

可以看到,最重要的就是this.webApplicationContext = initWebApplicationContext();
這段代碼,這個方法的作用是創建或刷新WebApplicationContext實例,并對servlet功能所使用的變量進行初始化:

image.png
image.png

可以看到上圖這段代碼將不會執行,直接到if(wac == null)中去了,

image.png

跳入findWebApplicationContext方法中,這個方法是用于根據ContextAttribute屬性加載WebApplicationContext,但這里可以看到ContextAttribute為空,所以這段代碼最終返回的還是null

image.png
image.png
image.png

接著走下一個if,

image.png
image.png
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    //獲取servlet初始化參數,如果沒有則默認為XMLWebApplicationContext.class
        Class<?> contextClass = getContextClass();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + getServletName() +
                    "' will try to create custom WebApplicationContext context of class '" +
                    contextClass.getName() + "'" + ", using parent context [" + parent + "]");
        }
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                    "Fatal initialization error in servlet with name '" + getServletName() +
                    "': custom WebApplicationContext class [" + contextClass.getName() +
                    "] is not of type ConfigurableWebApplicationContext");
        }
        //通過反射的方式實例化contextClass
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        //設置servlet環境
        wac.setEnvironment(getEnvironment());
        //這個parent使用的就是ContextLoaderListener初始化時創建的那個root WebApplicationContext
        wac.setParent(parent);
        //獲取ContextConfigLocation屬性,配置在servlet初始化參數中
        wac.setConfigLocation(getContextConfigLocation());
        //初始化spring環境包括加載配置文件等
        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }

上面是對createWebApplicationContext方法的一個詳細介紹,下面debug一步一步看這個方法的邏輯:

image.png
image.png
image.png

接著創建wac并配置了servlet初始化的一些參數后,初始化整個spring的環境:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // The application context id is still set to its original default value
            // -> assign a more useful id based on available information
            if (this.contextId != null) {
                wac.setId(this.contextId);
            }
            else {
                // Generate default id...
                ServletContext sc = getServletContext();
                if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
                    // Servlet <= 2.4: resort to name specified in web.xml, if any.
                    String servletContextName = sc.getServletContextName();
                    if (servletContextName != null) {
                        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
                                "." + getServletName());
                    }
                    else {
                        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
                    }
                }
                else {
                    // Servlet 2.5's getContextPath available!
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                            ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
                }
            }
        }

        wac.setServletContext(getServletContext());
        wac.setServletConfig(getServletConfig());
        wac.setNamespace(getNamespace());
        wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

        // the wac environment's #initPropertySources will be called in any case when
        // the context is refreshed; do it eagerly here to ensure servlet property sources
        // are in place for use in any post-processing or initialization that occurs
        // below prior to #refresh
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
        }

        postProcessWebApplicationContext(wac);

        applyInitializers(wac);
        //加載配置文件及整合parent到wac中
        wac.refresh();
    }

    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) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

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

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

上述代碼每一步都有自帶的注釋,相信很容易就能理解,其中的onRefresh方法是FrameworkServlet類中提供的模板方法,在子類dispatchservlet中進行了重寫,其主要作用就是為了刷新spring在web功能實現中必須使用的全局變量,這些變量在接下來的處理請求響應中將會用到,如下,就不詳細介紹了

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

    /**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        /**
        * 初始化MultipartResolver,主要用于處理文件上傳,默認情況下,spring是沒有Multipart處理的
        * 需要用戶自己去配置,常用配置如下:
        * <bean id="multipartResolver"
        *  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        * <property name="defaultEncoding" value="utf-8"></property>
        * <property name="maxUploadSize" value="10485760000"></property>
        * <property name="maxInMemorySize" value="40960"></property>
        * </bean>
        */
        initMultipartResolver(context);
        /**
        * 初始化LocaleResolver,用于國際化配置
        */
        initLocaleResolver(context);
        /**
        * 初始化ThemeResolver,主題theme用于控制網頁風格
        */
        initThemeResolver(context);
        /**
        * 初始化HandlerMappings,dispatchservlet會將Request請求提交給HandlerMapping然后HandlerMapping
        * 根據webapplicationcontext的配置來返回給dispatchservlet 相應的controller
        * 默認情況下,springMVC會加載當前系統中所有實現了HandlerMapping接口的bean
        */
        initHandlerMappings(context);
        /**
        * 初始化HandlerAdapters,適配器設計模式,HandlerAdapter適配當前請求到對應的控制器,
        * 
        */
        initHandlerAdapters(context);
        /**
        * 初始化HandlerExceptionResolvers,
        */
        initHandlerExceptionResolvers(context);
        /**
        * 初始化RequestToViewNameTranslator,當controller處理方法沒有返回一個view或者邏輯視圖名稱時,并且
        * 沒有在該方法中直接往response的輸出流中寫數據時,就會通過RequestToViewNameTranslator接口的實現類
        * 來提供一個約定好的邏輯視圖名稱供使用,spring中提供了一個默認的實現類
        */
        initRequestToViewNameTranslator(context);
        /**
        * 初始化ViewResolvers,當controller將請求處理結果放入到modelandview中后,dispatchservlet會根據
        * modelandview選擇合適的視圖進行渲染,springMVC通過ViewResolver接口定義的resolverViewName方法
        * 根據合適的viewname創建對應的view.
        * 配置如下:
        * <bean
        * class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        * <property name="prefix" value="/WEB-INF/views/" />
        * <property name="suffix" value=".jsp" />
        * </bean>
        */
        initViewResolvers(context);
        /**
        * 初始化FlashMapManager用于管理,FlashMapManager用于管理FlashMap,FlashMap用于保持flash attributes,
        * flash attributes提供了一個請求存儲屬性,在使用請求重定向時非常重要,flash attributes在重定向之前暫存
        * 以便重定向之后還能使用,并立即刪除.
        */
        initFlashMapManager(context);
    }

創建完WebApplicationContext 并刷新成功后,接著走下一步


image.png

發布wac

image.png

到此,dispatchservlet初始化完成,整個web工程才算啟動完成.

image.png

處理請求響應

完成了servlet的初始化過程后,現在可以進行對請求的處理響應過程了,打開瀏覽器地址欄輸入url;
http://localhost:8080/demo-idle-web/index.do

這個時候其實可以通過debug的信息簡略看下服務器是如何處理請求并與springMVC交互的:


上圖可以看到,從最下面的信息看起,可以看到jetty服務器先解析http請求,解析成HTTPServletRequest以及HTTPServletResponse后經過一系列邏輯處理后將request 與response傳遞給servlet容器,然后容器選擇對應的servlet進行處理request 與 response,這個時候其實就傳遞到了springMVC中的DispatchServlet中去了.
接下來繼續 debug:

image.png
image.png

繼續,跳入了doGet方法中,

image.png

deGet/doPost都沒有直接對請求進行處理,都是在processRequest方法中對請求進行處理的:

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

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        /**
        * 為了保證當前線程的LocaleContext屬性,RequestAttributes屬性可以再當前請求完成后還能恢復,
        */
        //提取當前線程LocaleContext屬性
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        //根據當前request創建對應的localeContext
        LocaleContext localeContext = buildLocaleContext(request);
        //提取當前線程對應的RequestAttributes屬性
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        //根據當前request創建對應的RequestAttributes
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        //將上述的根據request創建后的兩個屬性綁定到當前線程
        initContextHolders(request, localeContext, requestAttributes);

        try {
            //準備工作做完后,具體的處理邏輯委托給了子類dispatchServlet中的doService方法進行處理
            doService(request, response);
        }
        catch (ServletException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
            //請求處理結束后恢復線程到原始狀態
            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");
                    }
                }
            }
            //請求處理結束后無論成功與失敗都發布事件通知
            publishRequestHandledEvent(request, startTime, failureCause);
        }
    }

看了上面這段帶有注釋的代碼,相信對processRequest的處理邏輯應該是比較清楚了,這里額外講一點東西(僅僅是個人所了解的一些知識,可能不完全對):

 針對每個request請求,服務器都會分配一個線程進行處理,線程也不是無限的,頻繁的創建銷毀線程,
 進行線程上下文切換是非常消耗資源的,所以針對這些請求進行線程分配,一般來說都是通過線程池完成的,
 所以, 在請求處理完成后,是需要恢復線程到原始狀態的,刪除掉前一個request請求遺留的信息

接著debug進入doService方法中:

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

        // 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)) {
            logger.debug("Taking snapshot of request attributes before include");
            attributesSnapshot = new HashMap<String, Object>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                    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()) {
                return;
            }
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }

doService方法也是講具體的邏輯處理放入了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 {
                //如果是MultipartContent類型的request則轉換成MultipartHTTPServletRequest類型的Request
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                // Determine handler for the current request.
                //根據request信息尋找對應的handler
                mappedHandler = getHandler(processedRequest, false);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    //如果沒找到對應的handler則通過response返回錯誤信息
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                //根據當前的handler尋找對應的handlerAdapter
                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()) {
                        String requestUri = urlPathHelper.getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                try {
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }

                applyDefaultViewName(request, 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
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }

繼續跳入getHandler方法中,getHandler會通過request的信息從handlerMappings中提取對應的handler,其實就是提取了當前實例中的Controller的相關的信息,debug可以看到相關的信息:

,
{{[/index],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}
=
public java.lang.String com.axa.idle.controller.IdleController.heartBeatCode() throws java.lang.Exception}

可以看到 /index 與 controller中的方法IdleController.heartBeatCode() 有了一個映射關系,繼續debug,hm.getHandler這個方法中,這里就不花篇幅詳細解讀了,這一塊的邏輯處理挺長,但是都還挺簡單,容易理解,且源碼的注釋也是寫的比較詳細,這里簡單介紹下這一過程,

通過Request中的url等信息去匹配對應的controller,這里分一個直接匹配和通配符匹配的處理方式,
匹配完成后,將handler封裝成HandlerExecutionChain執行鏈,然后往執行鏈中加入攔截器,
以保證攔截器可以作用到目標對象中.

看到這個返回的handler的信息:

image.png

接著debug:

image.png

看名字就知道是個適配器設計模式,看下具體的邏輯,簡單易懂,遍歷所有的handlerAdapters,選擇適配的適配器:

image.png
image.png

接著debug,處理last-modified 請求頭緩存,客戶端第一次訪問url時會添加一個last-modified 的響應頭,客戶端第二次訪問url時,客戶端會向服務器發送請求頭"if-modified-since",詢問服務器該時間之后當前請求的內容是否修改過,如果無變化,則自動返回 304 狀態碼(只要響應頭,內容為空,節省服務器網絡帶寬).

image.png

繼續debug,攔截器攔截請求前置處理:

image.png

接著debug,處理邏輯:

image.png

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
debug step into 方法可以看到,跳入的是具體的哪個實現類(AbstractHandlerMethodAdapter):

image.png

看下該方法收到的具體的參數信息:


image.png
protected final ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            // Always prevent caching in case of session attribute management.
            //
            checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
        }
        else {
            // Uses configured default cacheSeconds setting.
            checkAndPrepare(request, response, true);
        }

        // Execute invokeHandlerMethod in synchronized block if required.
        //需要在session內的同步執行
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    return invokeHandleMethod(request, response, handlerMethod);
                }
            }
        }
        //調用用戶邏輯
        return invokeHandleMethod(request, response, handlerMethod);
    }

上面這段代碼重點在最后一句invokeHandleMethod(request, response, handlerMethod),這里就是執行具體的controller中的方法的邏輯了,該方法返回的是一個ModelAndView,這里的具體實現是通過將request解析以及提供的參數組合成controller中映射的方法所需要的參數,利用反射的方式調用該方法邏輯,計算執行結果,將返回結果再封裝到ModelAndView中,如下圖可以看到調用invokeHandleMethod方法會跳入controller中/index 映射的方法中去執行邏輯

image.png
image.png

返回結果封裝到ModelAndView中,由于heartBeatCode方法并沒有將任何執行結果放入model中,所以可以看到mv中view為index,model is {}:

image.png

接著debug:

image.png

applyDefaultViewName方法則是當mv中沒有view的值時,采用之前初始化時這個方法中提供的信息:

/**
* 初始化RequestToViewNameTranslator,當controller處理方法沒有返回一個view或者邏輯視圖名稱時,并且
* 沒有在該方法中直接往response的輸出流中寫數據時,就會通過RequestToViewNameTranslator接口的實現類
* 來提供一個約定好的邏輯視圖名稱供使用,spring中提供了一個默認的實現類
*/
initRequestToViewNameTranslator(context);

這個時候mv已經封裝好了,那么就是要做渲染視圖的事情了:

image.png
image.png

這段代碼邏輯篇幅有點長,這里就總結下resolveViewName實現了什么邏輯:
采用之前初始化時的ViewResolvers對視圖進行解析:

/**
        * 初始化ViewResolvers,當controller將請求處理結果放入到modelandview中后,dispatchservlet會根據
        * modelandview選擇合適的視圖進行渲染,springMVC通過ViewResolver接口定義的resolverViewName方法
        * 根據合適的viewname創建對應的view.
        * 配置如下:
        * <bean
        * class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        * <property name="prefix" value="/WEB-INF/views/" />
        * <property name="suffix" value=".jsp" />
        * </bean>
        */
        initViewResolvers(context);
image.png

然后解析視圖名時看當前的這個viewName是否在緩存中,在則直接從緩存中提取,提高效率,不在則直接創建該視圖,并且提供了對 redirect:xx 和 forward:xx 前綴的支持,最后向view中添加前綴以及后綴,并向view中添加了必要的屬性設置,view渲染完成后,接著是頁面跳轉了,

image.png
image.png

在renderMergedOutputModel方法中,主要就是完成了將model中的信息放入到Request中,這樣我們就可以在頁面中使用JSTL語法或者Request信息直接獲取的方式渲染頁面,這樣到達了我們通常在使用jsp頁面時采用JSTL的語法的方式獲取后臺返回過來的值渲染到頁面上.這一步最主要的就是通過將model中的值放入到Request中,這樣我們就可以在別的地方調用到這些值.看下頁面結果:

image.png

???????到此為止,我們就完成了整個springMVC處理Request請求響應的過程,整個過程中略過了一些東西,像異常視圖處理,url錯誤處理等等.

總結

總結一下整個springMVC的處理流程:

  ContextLoaderListener初始化WebApplicationContext ROOT,接著初始化servlet,
  初始化WebApplicationContext以及一些web應用中必須用到的屬性,初始化servlet完成后,
  整個web應用算是啟動成功了,接著開始處理請求,所有的請求都是通過dispatchservlet進行處理的,
  通過解析Request信息從handlermappings中找到對應handler(通常來說就是controller),封裝成一個
  包含攔截器的執行器鏈HandlerExecutionChain,然后找到對應的handlerAdapter適配器通過反射的方式
  調用controller中的方法進行邏輯處理,返回的結果封裝成ModelAndView,然后通過viewReslover對view
  進行試圖渲染,將model的值注入到Request中,最后返回response響應.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容