SpringMVC處理流程

在詳解SpringMVC處理流程之前渣淤,首先我們要做好準備工作,比如初始化SpringMVC容器吉嫩,如果SpringMVC和SpringMVC集成話价认,同樣也需要初始化Spring容器。

容器初始化

web.xml

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

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:applicationContext-*.xml</param-value>
</context-param>

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

從web.xml中可以看出通過ContextLoaderListener初始化Spring容器自娩,通過DispatcherServlet初始化SpringMVC容器用踩,SpringMVC容器作為Spring容器的子容器設(shè)置在Spring容器。

ContextLoaderListener的作用

初始ApplicationContext(默認的是XmlWebApplicationContext)然后將其放在ServletContext中。

    this.context = createWebApplicationContext(servletContext, parent);
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);

ServletContext作用

  1. 每一個web應(yīng)用都有一個 ServletContext與之相關(guān)聯(lián)捶箱。

  2. ServletContext對象在應(yīng)用啟動的被創(chuàng)建智什,在應(yīng)用關(guān)閉的時候被銷毀。

  3. ServletContext在全局范圍內(nèi)有效丁屎,類似于應(yīng)用中的一個全局變量荠锭。

DispatcherServlet作用

DispatcherServlet類圖

DispatcherServlet類圖

通過類圖可以看出,DispatcherServlet繼承了FrameworkServlet和HttpServletBean晨川。

HttpServletBean作用

HttpServletBean的作用主要是做一些初始化证九,將web.xml中配置的參數(shù)設(shè)置到Servlet中

//比如初始化init-param中的參數(shù)
<init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:dispatcher-servlet.xml</param-value> </init-param>
//源碼片段
HttpServletBean.ServletConfigPropertyValues ex = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ServletContextResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(ex, true);

FrameworkServlet作用

FrameworkServlet的作用講Servlet和Spring容器關(guān)聯(lián)。其實也就是初始化FrameworkServlet的屬性webApplicationContext共虑,這個屬性代表SpringMVC上下文愧怜,它有個父類上下文,既web.xml中配置的ContextLoaderListener監(jiān)聽器初始化的容器上下文妈拌。

//源碼片段
protected WebApplicationContext initWebApplicationContext() {
 //這個設(shè)置springMVC的父類上下文為ContextLoaderListener初始化的容器上下文
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;
        if(this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if(wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext attrName = (ConfigurableWebApplicationContext)wac;
                if(!attrName.isActive()) {
                    if(attrName.getParent() == null) {
                        attrName.setParent(rootContext);
                    }
                    this.configureAndRefreshWebApplicationContext(attrName);
                }
            }
        }
        if(wac == null) {
            wac = this.findWebApplicationContext();//一般返回的都是null
            //具體實現(xiàn)拥坛,獲取DispatcherServlet的applicationContext
            //WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName);
        }
        if(wac == null) {
            wac = this.createWebApplicationContext(rootContext);
        }
        if(!this.refreshEventReceived) {
            this.onRefresh(wac);
        }
        if(this.publishContext) {
            //attrName1=org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher
            String attrName1 = this.getServletContextAttributeName();
            //新創(chuàng)建的容器上下文設(shè)置到ServletContext中
            this.getServletContext().setAttribute(attrName1, wac);
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet \'" + this.getServletName() + "\' as ServletContext attribute with name [" + attrName1 + "]");
            }
        }
        return wac;
    }

DispatcherServlet主要組建

DispatcherServlet覆寫了FrameworkServlet中的onRefresh()方法,onRefresh()方法是鉤子方法尘分,子類可以重寫自己特有的方法猜惋。

//初始化DispatcherServlet使用的策略
protected void initStrategies(ApplicationContext context) {
    this.initMultipartResolver(context);
    this.initLocaleResolver(context);
    this.initThemeResolver(context);
    this.initHandlerMappings(context);
    this.initHandlerAdapters(context);
    this.initHandlerExceptionResolvers(context);
    this.initRequestToViewNameTranslator(context);
    this.initViewResolvers(context);
    this.initFlashMapManager(context);
}

SpringMVC處理流程

簡單的Demo

<!-- dispatcher-servlet.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- HandlerMapping -->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    <!-- HandlerAdapter -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    <!-- ViewResolver -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- 處理器 -->
    <bean name="/hello" class="HelloWorldController">
</beans>

<!-- HelloWorldController -->
public class HelloWorldController implements Controller {
    public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        //1、收集參數(shù)培愁、驗證參數(shù)
        //2著摔、綁定參數(shù)到命令對象
        //3、將命令對象傳入業(yè)務(wù)對象進行業(yè)務(wù)處理
        //4定续、選擇下一個頁面
        ModelAndView mv = new ModelAndView();
        //添加模型數(shù)據(jù) 可以是任意的POJO對象
        mv.addObject("message", "Hello World!");
        //設(shè)置邏輯視圖名谍咆,視圖解析器會根據(jù)該名字解析到具體的視圖頁面
        mv.setViewName("hello");
        return mv;
    }
}

處理流程

處理流程圖

SpringMVC處理流程
SpringMVC處理流程
  1. 用戶將發(fā)送請求至前端控制器DispatcherServlet

  2. DispatcherServlet收到請求調(diào)用HandlerMapping處理器映射器。

  3. 處理器映射器找到具體的處理器私股,生成處理器對象及處理器攔截器(如果有則生成)一并返回給DispatcherServlet摹察。

  4. DispatcherServlet調(diào)用HandlerAdapter處理器適配器

  5. HandlerAdapter經(jīng)過適配調(diào)用具體的處理器(Controller,也叫后端控制器)庇茫。

  6. Controller執(zhí)行完成返回ModelAndView

  7. HandlerAdapter將controller執(zhí)行結(jié)果ModelAndView返回給DispatcherServlet

  8. DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器

  9. ViewReslover解析后返回具體View

  10. DispatcherServlet根據(jù)View進行渲染視圖(即將模型數(shù)據(jù)填充至視圖中)港粱。

  11. DispatcherServlet響應(yīng)用戶

DispatcherServlet源碼

DispatcherServlet中最主要的核心功能是由doService()和doDispatch()實現(xiàn),接下來看一下他們的源碼

//doService()
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String requestUri = new UrlPathHelper().getRequestUri(request);
            logger.debug("DispatcherServlet with name '" + getServletName() +
                    "' processing 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 attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            logger.debug("Taking snapshot of request attributes before include");
            attributesSnapshot = new HashMap();
            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());

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

//doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        int interceptorIndex = -1;

        // Expose current LocaleResolver and request as LocaleContext.
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);

        // Expose current RequestAttributes to current thread.
        RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);

        if (logger.isTraceEnabled()) {
            logger.trace("Bound request context to thread: " + request);
        }
        
        try {
            ModelAndView mv = null;
            boolean errorView = false;

            try {
                processedRequest = checkMultipart(request);

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest, false);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Apply preHandle methods of registered interceptors.
                HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
                if (interceptors != null) {
                    for (int i = 0; i < interceptors.length; i++) {
                        HandlerInterceptor interceptor = interceptors[i];
                        if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
                            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
                            return;
                        }
                        interceptorIndex = i;
                    }
                }

                // Actually invoke the handler.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                // Do we need view name translation?
                if (mv != null && !mv.hasView()) {
                    mv.setViewName(getDefaultViewName(request));
                }

                // Apply postHandle methods of registered interceptors.
                if (interceptors != null) {
                    for (int i = interceptors.length - 1; i >= 0; i--) {
                        HandlerInterceptor interceptor = interceptors[i];
                        interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
                    }
                }
            }
            catch (ModelAndViewDefiningException ex) {
                logger.debug("ModelAndViewDefiningException encountered", ex);
                mv = ex.getModelAndView();
            }
            catch (Exception ex) {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(processedRequest, response, handler, ex);
                errorView = (mv != null);
            }

            // Did the handler return a view to render?
            if (mv != null && !mv.wasCleared()) {
                render(mv, processedRequest, response);
                if (errorView) {
                    WebUtils.clearErrorRequestAttributes(request);
                }
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +
                            getServletName() + "': assuming HandlerAdapter completed request handling");
                }
            }

            // Trigger after-completion for successful outcome.
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
        }

        catch (Exception ex) {
            // Trigger after-completion for thrown exception.
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
            throw ex;
        }
        catch (Error err) {
            ServletException ex = new NestedServletException("Handler processing failed", err);
            // Trigger after-completion for thrown exception.
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
            throw ex;
        }

        finally {
            // Clean up any resources used by a multipart request.
            if (processedRequest != request) {
                cleanupMultipart(processedRequest);
            }

            // Reset thread-bound context.
            RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
            LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);

            // Clear request attributes.
            requestAttributes.requestCompleted();
            if (logger.isTraceEnabled()) {
                logger.trace("Cleared thread-bound request context: " + request);
            }
        }
    }

再說DispatcherServlet

  1. 從上面的處理流程可以看出DispatcherServlet主要負責流程的控制旦签,它的主要職責如下:
  1. 文件上傳解析查坪,如果請求類型是multipart將通過MultipartResolver進行文件上傳解析;
  1. 通過HandlerMapping宁炫,將請求映射到處理器(返回一個HandlerExecutionChain偿曙,它包括一個處理器、多個HandlerInterceptor攔截器)羔巢;

  2. 通過HandlerAdapter支持多種類型的處理器(HandlerExecutionChain中的處理器)望忆;

  3. 通過ViewResolver解析邏輯視圖名到具體視圖實現(xiàn)罩阵;

  4. 本地化解析;

  5. 渲染具體的視圖等启摄;

  6. 如果執(zhí)行過程中遇到異常將交給HandlerExceptionResolver來解析稿壁。

  7. DispatcherServlet特殊中的Bean:

  1. Controller:處理器/頁面控制器,做的是MVC中的C的事情歉备,但控制邏輯轉(zhuǎn)移到前端控制器了傅是,用于對請求進行處理;
  1. HandlerMapping:請求到處理器的映射蕾羊,如果映射成功返回一個HandlerExecutionChain對象(包含一個Handler處理器(頁面控制器)對象喧笔、多個HandlerInterceptor攔截器)對象;如BeanNameUrlHandlerMapping將URL與Bean名字映射龟再,映射成功的Bean就是此處的處理器书闸;

  2. HandlerAdapter:HandlerAdapter將會把處理器包裝為適配器,從而支持多種類型的處理器利凑,即適配器設(shè)計模式的應(yīng)用浆劲,從而很容易支持很多類型的處理器;如SimpleControllerHandlerAdapter將對實現(xiàn)了Controller接口的Bean進行適配哀澈,并且diao處理器的handleRequest方法進行功能處理梳侨;

  3. ViewResolver:ViewResolver將把邏輯視圖名解析為具體的View,通過這種策略模式日丹,很容易更換其他視圖技術(shù);如InternalResourceViewResolver將邏輯視圖名映射為jsp視圖蚯嫌;

  4. LocalResover:本地化解析哲虾,因為Spring支持國際化,因此LocalResover解析客戶端的Locale信息從而方便進行國際化择示;

  5. ThemeResovler:主題解析束凑,通過它來實現(xiàn)一個頁面多套風格,即常見的類似于軟件皮膚效果栅盲;

  6. MultipartResolver:文件上傳解析汪诉,用于支持文件上傳;

  7. HandlerExceptionResolver:處理器異常解析谈秫,可以將異常映射到相應(yīng)的統(tǒng)一錯誤界面扒寄,從而顯示用戶友好的界面(而不是給用戶看到具體的錯誤信息);

  8. RequestToViewNameTranslator:當處理器沒有返回邏輯視圖名等相關(guān)信息時拟烫,自動將請求URL映射為邏輯視圖名该编;

  9. FlashMapManager:用于管理FlashMap的策略接口,F(xiàn)lashMap用于存儲一個請求的輸出硕淑,當進入另一個請求時作為該請求的輸入课竣,通常用于重定向場景嘉赎,后邊會細述。

  10. 攔截器的處理流程


    攔截器
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末于樟,一起剝皮案震驚了整個濱河市公条,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌迂曲,老刑警劉巖靶橱,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奢米,居然都是意外死亡抓韩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門鬓长,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谒拴,“玉大人,你說我怎么就攤上這事涉波∮⑸希” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵啤覆,是天一觀的道長苍日。 經(jīng)常有香客問我,道長窗声,這世上最難降的妖魔是什么相恃? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮笨觅,結(jié)果婚禮上拦耐,老公的妹妹穿的比我還像新娘。我一直安慰自己见剩,他們只是感情好杀糯,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苍苞,像睡著了一般固翰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上羹呵,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天骂际,我揣著相機與錄音,去河邊找鬼冈欢。 笑死方援,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的涛癌。 我是一名探鬼主播犯戏,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼送火,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了先匪?” 一聲冷哼從身側(cè)響起种吸,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呀非,沒想到半個月后坚俗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡岸裙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年猖败,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片降允。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡恩闻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出剧董,到底是詐尸還是另有隱情幢尚,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布翅楼,位于F島的核電站尉剩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏毅臊。R本人自食惡果不足惜理茎,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望管嬉。 院中可真熱鬧功蜓,春花似錦、人聲如沸宠蚂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽求厕。三九已至,卻和暖如春扰楼,著一層夾襖步出監(jiān)牢的瞬間呀癣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工弦赖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留项栏,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓蹬竖,卻偏偏與公主長得像沼沈,于是被迫代替她去往敵國和親流酬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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