SpringMVC源碼分析-DispatcherServlet

SpringMVC框架是Spring框架中web模塊死遭,時(shí)下常用來構(gòu)建web應(yīng)用裙盾。在應(yīng)用之余绞愚,也一直想要搞明白SpringMVC中是如何接受處理請(qǐng)求的?

SpingMVC 初始化

Spring框架和其他框架類似史翘,都是配置元素集中于xml配置文件中枉长,在框架初始化的時(shí)候,加載配置文件琼讽,解析文件必峰,生成對(duì)應(yīng)的配置。SpringMVC框架是依托于Spring容器钻蹬。Spring初始化的過程其實(shí)就是IoC容器啟動(dòng)的過程吼蚁,也就是上下文建立的過程。

ServletContext

每一個(gè)web應(yīng)用中都有一個(gè)Servlet上下文脉让。servlet容器提供一個(gè)全局上下文的環(huán)境桂敛,這個(gè)上下文環(huán)境將成為其他IoC容器的宿主環(huán)境,例如:WebApplicationContext就是作為ServletContext的一個(gè)屬性存在溅潜。

WebApplicationContext

在使用SpringMVC的時(shí)候,通常需要在web.xml文件中配置:

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

ContextLoaderListener 實(shí)現(xiàn)了ServletContextListener接口薪伏,在SpringMVC中作為監(jiān)聽器的存在滚澜,當(dāng)servlet容器啟動(dòng)時(shí)候,會(huì)調(diào)用contextInitialized進(jìn)行一些初始化的工作嫁怀。而ContextLoaderListenercontextInitialized的具體實(shí)現(xiàn)在ContextLoader類中设捐。


        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            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);
                }
            }
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

上面的部分代碼可以看出,初始化時(shí)候通過createWebApplicationContext(servletContext);聲明一個(gè)WebApplicationContext并賦值給ServletContextorg.springframework.web.context.WebApplicationContext.ROOT屬性塘淑,作為WebApplicationContext的根上下文(root context)萝招。

DispatcherServlet

在加載完<context-param><listener>之后,容器將加載配置了load-on-startupservlet存捺。

    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

DispatcherServlet在初始化的過程中槐沼,會(huì)建立一個(gè)自己的IoC容器上下文Servlet WebApplicationContext曙蒸,會(huì)以ContextLoaderListener建立的根上下文作為自己的父級(jí)上下文。DispatcherServlet持有的上下文默認(rèn)的實(shí)現(xiàn)類是XmlWebApplicationContext岗钩。Servlet有自己獨(dú)有的Bean空間纽窟,也可以共享父級(jí)上下文的共享Bean,當(dāng)然也存在配置有含有一個(gè)root WebApplicationContext配置兼吓。其關(guān)系如下圖所示臂港,后面也還會(huì)詳細(xì)介紹DispatcherServlet這個(gè)類。

DispatcherServlet類

DispatcherServlet最為SpringMVC核心類视搏,起到了前端控制器(Front controller)的作用审孽,負(fù)責(zé)請(qǐng)求分發(fā)等工作。

從類圖中可以看出浑娜,DispatcherServlet的繼承關(guān)系大致如此:

DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet -> GenericServlet

從繼承關(guān)系上可以得出結(jié)論佑力,DispatcherServlet本質(zhì)上還是一個(gè)ServletServlet的生命周期大致分為三個(gè)階段:

  • 初始化階段 init方法
  • 處理請(qǐng)求階段 service方法
  • 結(jié)束階段 destroy方法

這里就重點(diǎn)關(guān)注DispatcherServlet在這三個(gè)階段具體做了那些工作棚愤。

DispatcherServlet初始化

DispatcherServletinit()的實(shí)現(xiàn)在其父類HttpServletBean中搓萧。

    public final void init() throws ServletException {
        ...
        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            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) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

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

        ...
    }

以上部分源碼描述的過程是通過讀取<init-param>的配置元素,讀取到DispatcherServlet中宛畦,配置相關(guān)bean的配置瘸洛。完成配置后調(diào)用initServletBean方法來創(chuàng)建Servlet WebApplicationContext

initServletBean方法在FrameworkServlet類中重寫了:

    protected final void initServletBean() throws ServletException {
        ...

        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        ...
    }
    
    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            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) {
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            onRefresh(wac);
        }

        if (this.publishContext) {
            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;
    }

上文提到Servlet容器在啟動(dòng)的時(shí)候次和,通過ContextLoaderListener創(chuàng)建一個(gè)根上下文反肋,并配置到ServletContext中√な可以看出FrameworkServlet這個(gè)類做的作用是用來創(chuàng)建WebApplicationContext上下文的石蔗。大致過程如下:

  • 首先檢查webApplicationContext是否通過構(gòu)造函數(shù)注入,如果有的話畅形,直接使用养距,并將根上下文設(shè)置為父上下文。
  • 如果webApplicationContext沒有注入日熬,則檢查是否在ServletContext已經(jīng)注冊(cè)過棍厌,如果已經(jīng)注冊(cè)過,直接返回使用竖席。
  • 如果沒有注冊(cè)過耘纱,將重新新建一個(gè)webApplicationContext。將根上下文設(shè)置為父級(jí)上下文毕荐。
  • 不管是何種策略獲取的webApplicationContext束析,都將會(huì)調(diào)用onRefresh方法,onRefresh方法會(huì)調(diào)用initStrategies方法憎亚,通過上下文初始化HandlerMappings员寇、HandlerAdapters弄慰、ViewResolvers等等。
  • 最后丁恭,同樣會(huì)將所得webApplicationContext注冊(cè)到ServletContext中曹动。

initFrameworkServlet()默認(rèn)的實(shí)現(xiàn)是空的。這也可算是SpingMVC留的一個(gè)擴(kuò)展點(diǎn)牲览。

DispatcherServlet處理請(qǐng)求

縱觀SpringMVC的源碼墓陈,大量運(yùn)用模板方法的設(shè)計(jì)模式。Servletservice方法也不例外第献。FrameworkServlet類重寫service方法:

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

        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
            processRequest(request, response);
        }
        else {
            super.service(request, response);
        }
    }

如果請(qǐng)求的方法是PATCH或者空贡必,直接調(diào)用processRequest方法(后面會(huì)詳細(xì)解釋);否則庸毫,將調(diào)用父類的service的方法仔拟,即HttpServletservice方法, 而這里會(huì)根據(jù)請(qǐng)求方法,去調(diào)用相應(yīng)的doGet飒赃、doPost利花、doPut......

doXXX系列方法的實(shí)現(xiàn)并不是HttpServlet類中,而是在FrameworkServlet類中载佳。在FrameworkServletdoXXX系列實(shí)現(xiàn)中炒事,都調(diào)用了上面提到的processRequest方法:

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

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;

        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);

        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

        initContextHolders(request, localeContext, requestAttributes);

        try {
            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, response, startTime, failureCause);
        }
    }

為了避免子類重寫它,該方法用final修飾蔫慧。

  • 首先調(diào)用initContextHolders方法,將獲取到的localeContext挠乳、requestAttributesrequest綁定到線程上姑躲。
  • 然后調(diào)用doService方法睡扬,doService具體是由DispatcherServlet類實(shí)現(xiàn)的。
  • doService執(zhí)行完成后黍析,調(diào)用resetContextHolders卖怜,解除localeContext等信息與線程的綁定。
  • 最終調(diào)用publishRequestHandledEvent發(fā)布一個(gè)處理完成的事件阐枣。

DispatcherServlet類中的doService方法實(shí)現(xiàn)會(huì)調(diào)用doDispatch方法韧涨,這里請(qǐng)求分發(fā)處理的主要執(zhí)行邏輯。

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

doDispatch主要流程是:

  • 先判斷是否Multipart類型的請(qǐng)求侮繁。如果是則通過multipartResolver解析request
  • 通過getHandler方法找到從HandlerMapping找到該請(qǐng)求對(duì)應(yīng)的handler,如果沒有找到對(duì)應(yīng)的handler則拋出異常。
  • 通過getHandlerAdapter方法找到handler對(duì)應(yīng)的HandlerAdapter
  • 如果有攔截器如孝,執(zhí)行攔截器preHandler方法
  • HandlerAdapter執(zhí)行handle方法處理請(qǐng)求宪哩,返回ModelAndView
  • 如果有攔截器第晰,執(zhí)行攔截器postHandle方法
  • 然后調(diào)用processDispatchResult方法處理請(qǐng)求結(jié)果锁孟,封裝到response中彬祖。

SpingMVC 請(qǐng)求處理流程

SpringMVC框架是圍繞DispatcherServlet設(shè)計(jì)的。DispatcherServlet負(fù)責(zé)將請(qǐng)求分發(fā)給對(duì)應(yīng)的處理程序品抽。從網(wǎng)上找了兩個(gè)圖储笑,可以大致了解SpringMVC的框架對(duì)請(qǐng)求的處理流程。

  • 用戶發(fā)送請(qǐng)求圆恤,Front ControllerDispatcherServlet)根據(jù)請(qǐng)求信息將請(qǐng)求委托給對(duì)應(yīng)的Controller進(jìn)行處理突倍。
  • DispatcherServlet接收到請(qǐng)求后,HandlerMapping將會(huì)把請(qǐng)求封裝為HandlerExecutionChain盆昙,而HandlerExecutionChain包含請(qǐng)求的所有信息羽历,包括攔截器、Handler處理器等淡喜。
  • DispatcherServlet會(huì)找到對(duì)應(yīng)的HandlerAdapter秕磷,并調(diào)用對(duì)應(yīng)的處理方法,并返回一個(gè)ModelAndView對(duì)象炼团。
  • DispatcherServlet會(huì)將ModelAndView對(duì)象傳入View層進(jìn)行渲染澎嚣。
  • 最終DispatcherServlet將渲染好的response返回給用戶。

總結(jié)

本文主要分析SpringMVCDispatcherServlet的初始化瘟芝、請(qǐng)求流傳過程等易桃。

發(fā)現(xiàn)了SpringMVC中在DispatcherServlet的實(shí)現(xiàn)過程中運(yùn)用了模板方法設(shè)計(jì)模式,看到SpringMVC中留給用戶可擴(kuò)展的點(diǎn)也有很多模狭,體會(huì)到Open for extension, closed for modification的設(shè)計(jì)原則颈抚。

本文只關(guān)注了DispatcherServlet主流程,忽略了很多寶貴的細(xì)枝末節(jié)嚼鹉,如:HandlerMapping贩汉、HandlerExecutionChainHandlerAdapter等锚赤。后面有機(jī)會(huì)定會(huì)追本溯源匹舞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市线脚,隨后出現(xiàn)的幾起案子赐稽,更是在濱河造成了極大的恐慌,老刑警劉巖浑侥,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姊舵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡寓落,警方通過查閱死者的電腦和手機(jī)括丁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伶选,“玉大人史飞,你說我怎么就攤上這事尖昏。” “怎么了构资?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵抽诉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我吐绵,道長(zhǎng)迹淌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任拦赠,我火速辦了婚禮巍沙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荷鼠。我一直安慰自己句携,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布允乐。 她就那樣靜靜地躺著矮嫉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牍疏。 梳的紋絲不亂的頭發(fā)上蠢笋,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音鳞陨,去河邊找鬼昨寞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厦滤,可吹牛的內(nèi)容都是我干的援岩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼掏导,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼享怀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趟咆,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤添瓷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后值纱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳞贷,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年虐唠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悄晃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖妈橄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情翁脆,我是刑警寧澤眷蚓,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站反番,受9級(jí)特大地震影響沙热,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罢缸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一篙贸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枫疆,春花似錦爵川、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至值依,卻和暖如春圃泡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愿险。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工颇蜡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辆亏。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓风秤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親褒链。 傳聞我的和親對(duì)象是個(gè)殘疾皇子唁情,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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