debug方式深入springMVC源碼的實現(xiàn)

前言

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

服務(wù)器原理簡介

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

SpringMVC

開始詳解SpringMVC了.

1、web.xml

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

2、啟動

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

如下所示,web.xml中配置的監(jiān)聽器類:

image.png

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


image.png

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

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //驗證context是否已經(jīng)存在,
        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);
                    }
                    //刷新上下文環(huán)境
                    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) {
                //映射當(dāng)前的類加載器與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行,創(chuàng)建WebApplicationContext,

image.png

debug 跳入createWebApplicationContext(servletContext)方法中,

image.png

determineContextClass方法返回一個WebApplicationContext 接口的實現(xiàn)類,否則默認(rèn)返回XmlWebApplicationContext 或者一個指定的context

image.png

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


image.png
image.png

image.png

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

image.png

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


image.png

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


image.png
初始化servlet
   SpringMVC通過DispatcherServlet對請求進(jìn)行處理,而DispatcherServlet是實現(xiàn)了Servlet接口的,而servlet接口是基于servlet規(guī)范編寫的一個Java類,實際上一個servlet會經(jīng)歷三個階段:初始化階段麻诀、運行階段、銷毀階段,也就是我們熟知的servlet 的init、doGet/doPost颂砸、destroy這三個方法的執(zhí)行,而dispatchservlet是實現(xiàn)了servlet接口的疑务,那么必然也會經(jīng)歷這三個階段,下面是DispatchServlet類的父類結(jié)構(gòu)圖:
image.png

可以看到dispatServlet的父類FrameworkServlet梗醇,F(xiàn)rameworkServlet又繼承了HttpServletBean知允,實際上整個servlet的三個階段都在這三個類中做了具體的實現(xiàn)叙谨。
初始化階段在HttpServletBean中可以找到相應(yīng)的方法,

@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);
            //將當(dāng)前這個Servlet類轉(zhuǎn)化為BeanWrapper,從而能以spring的方式對init parameters的值進(jìn)行注入
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            //注冊自定義屬性編輯器,一旦遇到resource類型的屬性將會使用ResourceEditor進(jìn)行解析
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            //空實現(xiàn),留給子類覆蓋
            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進(jìn)入init方法中,跳入FrameworkServlet的initServletBean方法中,如下圖

image.png

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

image.png
image.png

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

image.png

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

image.png
image.png
image.png

接著走下一個if,

image.png
image.png
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    //獲取servlet初始化參數(shù),如果沒有則默認(rèn)為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);
        //設(shè)置servlet環(huán)境
        wac.setEnvironment(getEnvironment());
        //這個parent使用的就是ContextLoaderListener初始化時創(chuàng)建的那個root WebApplicationContext
        wac.setParent(parent);
        //獲取ContextConfigLocation屬性,配置在servlet初始化參數(shù)中
        wac.setConfigLocation(getContextConfigLocation());
        //初始化spring環(huán)境包括加載配置文件等
        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }

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

image.png
image.png
image.png

接著創(chuàng)建wac并配置了servlet初始化的一些參數(shù)后,初始化整個spring的環(huán)境:

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中進(jìn)行了重寫,其主要作用就是為了刷新spring在web功能實現(xiàn)中必須使用的全局變量,這些變量在接下來的處理請求響應(yīng)中將會用到,如下,就不詳細(xì)介紹了

    @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,主要用于處理文件上傳,默認(rèn)情況下,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用于控制網(wǎng)頁風(fēng)格
        */
        initThemeResolver(context);
        /**
        * 初始化HandlerMappings,dispatchservlet會將Request請求提交給HandlerMapping然后HandlerMapping
        * 根據(jù)webapplicationcontext的配置來返回給dispatchservlet 相應(yīng)的controller
        * 默認(rèn)情況下,springMVC會加載當(dāng)前系統(tǒng)中所有實現(xiàn)了HandlerMapping接口的bean
        */
        initHandlerMappings(context);
        /**
        * 初始化HandlerAdapters,適配器設(shè)計模式,HandlerAdapter適配當(dāng)前請求到對應(yīng)的控制器,
        * 
        */
        initHandlerAdapters(context);
        /**
        * 初始化HandlerExceptionResolvers,
        */
        initHandlerExceptionResolvers(context);
        /**
        * 初始化RequestToViewNameTranslator,當(dāng)controller處理方法沒有返回一個view或者邏輯視圖名稱時,并且
        * 沒有在該方法中直接往response的輸出流中寫數(shù)據(jù)時,就會通過RequestToViewNameTranslator接口的實現(xiàn)類
        * 來提供一個約定好的邏輯視圖名稱供使用,spring中提供了一個默認(rèn)的實現(xiàn)類
        */
        initRequestToViewNameTranslator(context);
        /**
        * 初始化ViewResolvers,當(dāng)controller將請求處理結(jié)果放入到modelandview中后,dispatchservlet會根據(jù)
        * modelandview選擇合適的視圖進(jìn)行渲染,springMVC通過ViewResolver接口定義的resolverViewName方法
        * 根據(jù)合適的viewname創(chuàng)建對應(yīng)的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);
    }

創(chuàng)建完WebApplicationContext 并刷新成功后,接著走下一步


image.png

發(fā)布wac

image.png

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

image.png

處理請求響應(yīng)

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

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


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

image.png
image.png

繼續(xù),跳入了doGet方法中,

image.png

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

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

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

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

        try {
            //準(zhǔn)備工作做完后,具體的處理邏輯委托給了子類dispatchServlet中的doService方法進(jìn)行處理
            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 {
            //請求處理結(jié)束后恢復(fù)線程到原始狀態(tài)
            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");
                    }
                }
            }
            //請求處理結(jié)束后無論成功與失敗都發(fā)布事件通知
            publishRequestHandledEvent(request, startTime, failureCause);
        }
    }

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

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

接著debug進(jìn)入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則轉(zhuǎn)換成MultipartHTTPServletRequest類型的Request
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

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

                // Determine handler adapter for the current request.
                //根據(jù)當(dāng)前的handler尋找對應(yīng)的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);
            }
        }
    }

繼續(xù)跳入getHandler方法中,getHandler會通過request的信息從handlerMappings中提取對應(yīng)的handler,其實就是提取了當(dāng)前實例中的Controller的相關(guān)的信息,debug可以看到相關(guān)的信息:

,
{{[/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() 有了一個映射關(guān)系,繼續(xù)debug,hm.getHandler這個方法中,這里就不花篇幅詳細(xì)解讀了,這一塊的邏輯處理挺長,但是都還挺簡單,容易理解,且源碼的注釋也是寫的比較詳細(xì),這里簡單介紹下這一過程,

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

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

image.png

接著debug:

image.png

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

image.png
image.png

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

image.png

繼續(xù)debug,攔截器攔截請求前置處理:

image.png

接著debug,處理邏輯:

image.png

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

image.png

看下該方法收到的具體的參數(shù)信息:


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內(nèi)的同步執(zhí)行
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    return invokeHandleMethod(request, response, handlerMethod);
                }
            }
        }
        //調(diào)用用戶邏輯
        return invokeHandleMethod(request, response, handlerMethod);
    }

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

image.png
image.png

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

image.png

接著debug:

image.png

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

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

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

image.png
image.png

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

/**
        * 初始化ViewResolvers,當(dāng)controller將請求處理結(jié)果放入到modelandview中后,dispatchservlet會根據(jù)
        * modelandview選擇合適的視圖進(jìn)行渲染,springMVC通過ViewResolver接口定義的resolverViewName方法
        * 根據(jù)合適的viewname創(chuàng)建對應(yīng)的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

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

image.png
image.png

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

image.png

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

總結(jié)

總結(jié)一下整個springMVC的處理流程:

  ContextLoaderListener初始化WebApplicationContext ROOT,接著初始化servlet,
  初始化WebApplicationContext以及一些web應(yīng)用中必須用到的屬性,初始化servlet完成后,
  整個web應(yīng)用算是啟動成功了,接著開始處理請求,所有的請求都是通過dispatchservlet進(jìn)行處理的,
  通過解析Request信息從handlermappings中找到對應(yīng)handler(通常來說就是controller),封裝成一個
  包含攔截器的執(zhí)行器鏈HandlerExecutionChain,然后找到對應(yīng)的handlerAdapter適配器通過反射的方式
  調(diào)用controller中的方法進(jìn)行邏輯處理,返回的結(jié)果封裝成ModelAndView,然后通過viewReslover對view
  進(jìn)行試圖渲染,將model的值注入到Request中,最后返回response響應(yīng).
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市匆绣,隨后出現(xiàn)的幾起案子驻右,更是在濱河造成了極大的恐慌,老刑警劉巖崎淳,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堪夭,死亡現(xiàn)場離奇詭異,居然都是意外死亡拣凹,警方通過查閱死者的電腦和手機(jī)森爽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咐鹤,“玉大人拗秘,你說我怎么就攤上這事∑砘蹋” “怎么了雕旨?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捧请。 經(jīng)常有香客問我凡涩,道長,這世上最難降的妖魔是什么疹蛉? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任活箕,我火速辦了婚禮,結(jié)果婚禮上可款,老公的妹妹穿的比我還像新娘育韩。我一直安慰自己,他們只是感情好闺鲸,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布筋讨。 她就那樣靜靜地躺著,像睡著了一般摸恍。 火紅的嫁衣襯著肌膚如雪悉罕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天立镶,我揣著相機(jī)與錄音壁袄,去河邊找鬼。 笑死媚媒,一個胖子當(dāng)著我的面吹牛嗜逻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缭召,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼变泄,長吁一口氣:“原來是場噩夢啊……” “哼令哟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妨蛹,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤屏富,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蛙卤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狠半,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年颤难,在試婚紗的時候發(fā)現(xiàn)自己被綠了神年。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡行嗤,死狀恐怖已日,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栅屏,我是刑警寧澤飘千,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站栈雳,受9級特大地震影響护奈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哥纫,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一霉旗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛀骇,春花似錦厌秒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雕欺,卻和暖如春岛马,著一層夾襖步出監(jiān)牢的瞬間棉姐,已是汗流浹背屠列。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留伞矩,地道東北人笛洛。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像乃坤,于是被迫代替她去往敵國和親苛让。 傳聞我的和親對象是個殘疾皇子沟蔑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容