詳解Spring MVC:下

?前面分析了Spring MVC的創(chuàng)建過(guò)程怎炊,本章分析Spring MVC是怎么處理請(qǐng)求的空繁。我們這里分兩步:首先分析HttpServletBean、FrameworkServlet和DispathcerServlet這三個(gè)Servlet的處理過(guò)程草戈,這樣大家可以明白從Servlet容器將請(qǐng)求交給Spring MVC一直到DispatcherServlet具體處理請(qǐng)求之前都做了些什么只祠,最后再重點(diǎn)分析Spring MVC中最核心的處理方法doDispatch的結(jié)構(gòu)。

10.1 HttpServletBean

?HttpServletBean主要參與了創(chuàng)建工作,并沒(méi)有涉及請(qǐng)求的處理勉盅。之所以單獨(dú)將它列出來(lái)是為了明確地告訴大家這里沒(méi)有具體處理請(qǐng)求。

10.2 FrameworkServlet

?前面講過(guò)Servlet的處理過(guò)程:首先是從Servlet接口的service方法開(kāi)始顶掉,然后在HttpServlet的service方法中根據(jù)請(qǐng)求的類(lèi)型不同將請(qǐng)求路由到了doGet草娜、doHead、doPost痒筒、doPut宰闰、doDelete、doOptions和doTrace七個(gè)方法簿透,并且做了doHead移袍、doOptions和doTrace的默認(rèn)實(shí)現(xiàn),其中doHead調(diào)用doGet老充,然后返回只有header沒(méi)有body的response葡盗。
?在FrameworkServlet中重寫(xiě)了service、doGet啡浊、doPost觅够、doPut、doDelete巷嚣、doOptions喘先、doTrace方法(除了doHead的所有處理請(qǐng)求的方法)。在service方法中增加了對(duì)PATCH類(lèi)型請(qǐng)求對(duì)處理廷粒,其他類(lèi)型的請(qǐng)求交給父類(lèi)進(jìn)行處理窘拯;doOptions和doTrace方法可以通過(guò)設(shè)置dispatchOptionsRequest和dispatchTraceRequest參數(shù)決定是自己處理還是交給父類(lèi)處理(默認(rèn)都是交給父親類(lèi)處理,doOptions會(huì)在父類(lèi)的處理結(jié)果中增加PATCH類(lèi)型)坝茎;doGet涤姊、doPost、doPut和doDelete都是自己處理嗤放。所有需要自己處理對(duì)請(qǐng)求都交給了processRequest方法進(jìn)行統(tǒng)一處理砂轻。
?下面來(lái)看一下service和doGet的代碼,別對(duì)需要自己處理對(duì)方法都和doGet類(lèi)似斤吐。

@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
    /**
     * Override the parent class implementation in order to intercept PATCH requests.
     */
    @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);
        }
    }
    
    ...
    
    /**
     * Delegate GET requests to processRequest/doService.
     * <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
     * with a {@code NoBodyResponse} that just captures the content length.
     * @see #doService
     * @see #doHead
     */
    @Override
    protected final void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        processRequest(request, response);
    }
    
    ...
    
}

?我們發(fā)現(xiàn)這里所做的事情跟HttpServlet里將不同類(lèi)型的請(qǐng)求路由到不通方法進(jìn)行處理的思路正好相反搔涝,這里又將所有的請(qǐng)求合并到了processRquest方法。當(dāng)然并不是說(shuō)Spring MVC中就不對(duì)request的類(lèi)型進(jìn)行分類(lèi)和措,而全部執(zhí)行相同對(duì)操作了庄呈,恰恰相反,Spring MVC中對(duì)不用類(lèi)型請(qǐng)求的支持非常好派阱,不過(guò)它是通過(guò)另外一種方式進(jìn)行處理的诬留,它將不同類(lèi)型的請(qǐng)求用不同的Handler進(jìn)行處理,后面再詳細(xì)分析。
?可能有的讀者會(huì)想文兑,直接覆蓋了service不是就可以了嗎盒刚?HttpServlet是在service方法中將請(qǐng)求路由到不同的方法,如果在service中不再調(diào)用super.service()绿贞,而是直接將請(qǐng)求交給processRequest處理不是更簡(jiǎn)單嗎因块?從現(xiàn)在的結(jié)構(gòu)來(lái)看確實(shí)如此,不過(guò)那么做其實(shí)存在著一些問(wèn)題籍铁。比如涡上,我們?yōu)榱四撤N特殊需求需要在Post請(qǐng)求處理前對(duì)request做一些處理,這時(shí)可能會(huì)新建一個(gè)繼承自DispactherServlet的類(lèi)拒名,然后覆蓋doPost方法吩愧,在里面先對(duì)request做處理,然后再調(diào)用super.doPost()增显,但是按正常的邏輯雁佳,調(diào)用doPost應(yīng)該可以完成才合理,而且一般情況下開(kāi)發(fā)者并不需要對(duì)Spring MVC的結(jié)構(gòu)非常了解同云,所以Spring MVC的這種做法雖然看起來(lái)有點(diǎn)笨拙但是必要對(duì)甘穿。
?下面就來(lái)看processRequest方法,processRequest是FrameworkServlet類(lèi)在處理請(qǐng)求中最核心對(duì)方法梢杭。

@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
    /**
     * Process this request, publishing an event regardless of the outcome.
     * <p>The actual event handling is performed by the abstract
     * {@link #doService} template method.
     */
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        //獲取LocaleContext中原來(lái)保存的LocaleContext
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        //獲取當(dāng)前請(qǐng)求的LocaleContext
        LocaleContext localeContext = buildLocaleContext(request);
        //獲取RequestContextHolder中原來(lái)保存的RequestAttributes
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        //獲取當(dāng)前請(qǐng)求的ServletRequestAttributes
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        //將當(dāng)前請(qǐng)求的LocaleContext和ServletRequestAttributes設(shè)置到LocaleContextHolder和RequestContextHolder
        initContextHolders(request, localeContext, requestAttributes);

        try {
            //實(shí)際處理請(qǐng)求入口
            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 {
            //恢復(fù)原來(lái)的LocaleContext和ServletAttributes到LocaleContextHolder和RequestContextHolder中
            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");
                    }
                }
            }
            //發(fā)布ServletRequestHandlerEvent消息
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }
    
    ...
}

?processRequest方法中的核心語(yǔ)句是doService(request, response)温兼,這是一個(gè)模版方法,在DispatcherServlet中具體實(shí)現(xiàn)武契。在doService前后還做了一些事情(也就是大家熟悉到裝飾模式):首先獲取了LocaleContextHolder和RequestContextHolder中原來(lái)保存的LocaleContext和RequestAttributes并設(shè)置到了previousLocaleContext和previousAttributes臨時(shí)屬性募判,然后調(diào)用buildLocaleContext和buildReuqestAttributes方法獲取到當(dāng)前請(qǐng)求的LocaleContext和RequestAttributes,并通過(guò)initContextHolders方法將它們?cè)O(shè)置到LocaleContextHolder和RequestContextHolder中(處理完請(qǐng)求后再恢復(fù)到原來(lái)的值)咒唆,接著使用request拿到異步處理管理器并設(shè)置了攔截器届垫,做完這些后執(zhí)行了doService方法,執(zhí)行完后全释,最后(finally中)通過(guò)resetContextHolders方法將原來(lái)到previousLocaleContext和previousAttributes恢復(fù)到LocaleContextHolder和RequestContextHolder中装处,并調(diào)用publishReuqestHandledEvent方法發(fā)布了一個(gè)ServletRequestHandledEvent類(lèi)型的消息。
?這里涉及了異步請(qǐng)求相關(guān)的內(nèi)容浸船,Spring MVC中異步請(qǐng)求的內(nèi)容會(huì)在后面專(zhuān)門(mén)講解妄迁。除了異步請(qǐng)求和調(diào)用doService方法具體處理請(qǐng)求,processRequest自己主要做了兩件事情:1??對(duì)LocaleContext和Requestttributes的設(shè)置及恢復(fù)李命;2??處理完后發(fā)布了ServletRequestHandledEvent消息登淘。
?首先來(lái)看一下LocaleContext和RequestAttributes。LocaleContext里面存放著Locale(本地化信息封字,如zh-cn等)黔州,RequestAttributes是spring的一個(gè)接口耍鬓,通過(guò)它可以get/set/removeAttributes類(lèi),在ServletRequestAttributes里面還封裝了request\response和session流妻,而且都提供了get方法牲蜀,可以直接獲取。下面來(lái)看一下ServletRequestAttributes里setAttribute的代碼(get/remove都大同小異)绅这。

public class ServletRequestAttributes extends AbstractRequestAttributes {
    ...
    @Override
    public void setAttribute(String name, Object value, int scope) {
        if (scope == SCOPE_REQUEST) {
            if (!isRequestActive()) {
                throw new IllegalStateException(
                        "Cannot set request attribute - request is not active anymore!");
            }
            this.request.setAttribute(name, value);
        }
        else {
            HttpSession session = obtainSession();
            this.sessionAttributesToUpdate.remove(name);
            session.setAttribute(name, value);
        }
    }
    ...
}

?設(shè)置屬性時(shí)可以通過(guò)scope判斷是對(duì)request還是session進(jìn)行設(shè)置涣达,具體對(duì)設(shè)置方法非常簡(jiǎn)單,就是直接對(duì)request和session操作君躺,sessionAttributesToUpdate屬性后面講到SessionAttributesHandler的時(shí)候再介紹峭判,這里可以先不考慮它开缎。需要注意的是isRequestActive方法棕叫,當(dāng)調(diào)用了ServletRequestAttributes的requestCompleted方法后requestActive就會(huì)變稱(chēng)false,執(zhí)行之前是true奕删。這個(gè)很容易理解俺泣,request執(zhí)行完了,當(dāng)然也就不能再對(duì)它進(jìn)行操作了完残!你可能已經(jīng)注意到伏钠,在剛才的finally塊中已調(diào)用requestAttributes的requestCompleted方法。
?現(xiàn)在大家對(duì)LocaleContext和RequestAttributes已經(jīng)有了大概對(duì)了解谨设,前者可以獲取Locale熟掂,后者用于管理request和session的屬性。不過(guò)可能還是有種沒(méi)有理解透的感覺(jué)扎拣,因?yàn)檫€不知道它到底怎么用赴肚。不要著急,我們接下來(lái)看LocaleContextHolder和RequestContextHolder二蓝,把這兩個(gè)理解了也就全明白了誉券!
?先來(lái)看LocaleContextHolder,這是一個(gè)abstract類(lèi)刊愚,不過(guò)里面對(duì)方法都是static的踊跟,可以直接調(diào)用,而且沒(méi)有父類(lèi)也沒(méi)有子類(lèi)鸥诽!也就是說(shuō)我們不能對(duì)它實(shí)例化商玫,只能調(diào)用其定義的static方法。這種abstract的使用方式也值得我們學(xué)習(xí)牡借。在LocaleContextHolder中定義了兩個(gè)static的屬性决帖。

public abstract class LocaleContextHolder {

    private static final ThreadLocal<LocaleContext> localeContextHolder =
            new NamedThreadLocal<>("LocaleContext");

    private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
            new NamedInheritableThreadLocal<>("LocaleContext");
    ...
}

?這兩個(gè)屬性都是ThreadLocal<LocaleContext>類(lèi)型的,LocaleContext前面已經(jīng)介紹了蓖捶,ThreadLocal大家應(yīng)該也不陌生地回,很多地方都用了它。
?LocaleContextHolder類(lèi)里面封裝了兩個(gè)屬性localeContextHodler和inheritableLocaleContextHolder,它們都是LocaleContext刻像,其中第二個(gè)可以被子線程繼承畅买。LocaleContextHolder還提供get/set方法苫亦,可以獲取和設(shè)置LocaleContext姜挺,另外還提供了get/setLocale方法,可以直接操作Locale痕貌,當(dāng)然都是static的溜徙。這個(gè)使用起來(lái)非常方便湃缎!比如,在程序中需要用到Locale的時(shí)候蠢壹,首先想到的可能是request.getLocale()嗓违,這是最直接的方法。不過(guò)有時(shí)候在service層需要用到Locale的時(shí)候图贸,再用這種方法就不方便了蹂季,因?yàn)檎?lái)說(shuō)service層是沒(méi)有request的,這時(shí)可能就需要在controller層將Locale拿出來(lái)疏日,然后再傳進(jìn)去了偿洁!當(dāng)然這也沒(méi)什么,傳一下就好了沟优,但最重要的是怎么傳呢涕滋?服務(wù)層的代碼可能已經(jīng)通過(guò)測(cè)試了,如果想將Locale傳進(jìn)行可能就需要改接口挠阁,而修改接口可能會(huì)引起很多問(wèn)題宾肺!而有g(shù)etLocale()就可以了,它是靜態(tài)方法鹃唯,可以直接調(diào)用爱榕!當(dāng)然,在Spring MVC中Locale的值并不總是request.getLocale()獲取到的值坡慌,而是采用了非常靈活的機(jī)制黔酥,在后面的LocaleResolver中再詳細(xì)講解。
?RequestContextHolder也是一樣的道理洪橘,里面封裝了RequestAttributes跪者,可以get/set/removeAttribute,而且因?yàn)閷?shí)際封裝的是ServletRequestAttributes熄求,所以還可以getRequest渣玲、getResponse、getSession弟晚!這樣就可以在任何地方都能方便地獲取這些對(duì)象了忘衍!另外逾苫,因?yàn)槔锩娣庋b的其實(shí)是對(duì)象都引用,所以即使在doService方法里面設(shè)置Attribute枚钓,使用RequestContextHoler也一樣可以獲取到铅搓。
?在方法最后的finally中調(diào)用resetContextHolders方法將原來(lái)的LocaleContext和RequestAttributes又恢復(fù)了。這是因?yàn)樵赟ervlet外面可能還有別的操作搀捷,如Filter(Spring-MVC自己的HandlerInterceptor是在doService內(nèi)部的)等星掰,為了不影響那些操作,所以需要進(jìn)行恢復(fù)嫩舟。
?最后就是publishRequestHandledEvent(request, response, startTime, failureCause)發(fā)布消息了氢烘。在publishRequestHandledEvent內(nèi)部發(fā)布了一個(gè)ServletRequestHandledEvent消息,代碼如下:

@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
    private void publishRequestHandledEvent(
            HttpServletRequest request, HttpServletResponse response, long startTime, Throwable failureCause) {
        //publishEvents可以在配置Servlet時(shí)設(shè)置家厌,默認(rèn)為true
        if (this.publishEvents) {
            //無(wú)論請(qǐng)求是否執(zhí)行成功都發(fā)布消息
            // Whether or not we succeeded, publish an event.
            long processingTime = System.currentTimeMillis() - startTime;
            int statusCode = (responseGetStatusAvailable ? response.getStatus() : -1);
            this.webApplicationContext.publishEvent(
                    new ServletRequestHandledEvent(this,
                            request.getRequestURI(), request.getRemoteAddr(),
                            request.getMethod(), getServletConfig().getServletName(),
                            WebUtils.getSessionId(request), getUsernameForRequest(request),
                            processingTime, failureCause, statusCode));
        }
    }
}

?當(dāng)publishEvents設(shè)置為true時(shí)播玖,請(qǐng)求處理結(jié)束后就會(huì)發(fā)出這個(gè)消息,無(wú)論請(qǐng)求處理成功與否都會(huì)發(fā)布像街。publishEvents可以在web.xml文件中配置Spring MVC的Servlet時(shí)配置黎棠,默認(rèn)為true時(shí)晋渺。我們可以通過(guò)監(jiān)聽(tīng)這個(gè)事件來(lái)做一些事情镰绎,如記錄日志。
?下面就寫(xiě)一個(gè)日志的監(jiān)聽(tīng)器木西。

/**
 * 記錄日志的監(jiān)聽(tīng)器
 *
 * @version
 * @author kyle 2018年9月15日上午11:08:53
 * @since 1.8
 */
@Component
public class ServletRequestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> {
    final static Logger logger = LoggerFactory.getLogger("RequestProcessLog");

    @Override
    public void onApplicationEvent(ServletRequestHandledEvent event) {
        logger.info(event.getDescription());
    }

}

?我們可以看到畴栖,只要簡(jiǎn)單地繼承ApplicationListener,并且把自己要做的事情寫(xiě)到onApplicationEvent里面就行了八千。很簡(jiǎn)單吧吗讶!當(dāng)然要把它注冊(cè)到spring容器里才能起作用,如果開(kāi)啟了注釋?zhuān)灰陬?lèi)上面標(biāo)注@Component就可以了恋捆。
?到現(xiàn)在為止FrameworkServlet就分析完了照皆,我們?cè)俸?jiǎn)單地回顧一下:首先是在service方法里添加了對(duì)PATCH的處理,并將所有需要自己處理的請(qǐng)求都集中到了processRequest方法進(jìn)行統(tǒng)一處理沸停,這和HttpServlet里面根據(jù)request的類(lèi)型將請(qǐng)求分配到各個(gè)不同的方法進(jìn)行處理的過(guò)程正好相反膜毁。
?然后就是processRequest方法,在processRequest里面主要的處理邏輯交給了doService愤钾,這是一個(gè)模版方法瘟滨,在子類(lèi)中具體實(shí)現(xiàn),另外就是對(duì)使用當(dāng)前request獲取到的LocaleContext和RequestAttributes進(jìn)行了保存能颁,以及處理完之后的恢復(fù)杂瘸,在最后發(fā)布了ServletRequestHandledEvent事件。

10.3 DispatcherServlet

?DispatcherServlet是Spring MVC最核心的類(lèi)伙菊,整個(gè)處理過(guò)程的頂層設(shè)計(jì)都在這里败玉,所以我們一定要把這個(gè)類(lèi)徹底弄明白敌土。
?通過(guò)之前的分析我們知道,DispatcherServlet里面執(zhí)行處理的入口方法應(yīng)該是doService运翼,不過(guò)doService并沒(méi)有直接進(jìn)行處理纯赎,而是交給了doDispatch進(jìn)行具體的處理,在doDispacth處理前doService做了一些事情:首先判斷是不是include請(qǐng)求南蹂,如果是則對(duì)request的Attribute做一個(gè)快照備份犬金,等doDispacth處理完之后(如果不是異步調(diào)用且未完成)進(jìn)行還原,在做完快照又對(duì)request設(shè)置了一些屬性六剥,代碼如下:

@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
    ...
    /**
     * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
     * for the actual dispatching.
     */
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                    " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }

        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        //當(dāng)include請(qǐng)求時(shí)對(duì)request的Attribute做快照備份
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<String, Object>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }

        // Make framework objects available to handlers and view objects.
        //對(duì)request設(shè)置一些屬性
        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()) {
                // Restore the original attribute snapshot, in case of an include.
                //還原request快照的屬性
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }
    ...
}

?對(duì)request設(shè)置的屬性中晚顷,前面4個(gè)屬性webApplicationContext、localeResolver疗疟、themeResolver和themeSource在之后介紹的handler和view中需要使用该默,到時(shí)候再作分析。后面三個(gè)屬性都和flashMap相關(guān)策彤,主要用于Redirect轉(zhuǎn)發(fā)時(shí)參數(shù)的傳遞栓袖,比如,為了避免重復(fù)提交表單店诗,可以在處理完post請(qǐng)求后redirect到一個(gè)get的請(qǐng)求裹刮,這樣即使用戶(hù)刷新也不會(huì)有重復(fù)提交的問(wèn)題。不過(guò)這里有個(gè)問(wèn)題庞瘸,前面的post請(qǐng)求是提交訂單捧弃,提交完后redirect到一個(gè)顯示訂單的頁(yè)面,顯然在顯示訂單的頁(yè)面需要知道訂單的一些信息擦囊,但redirect本身是沒(méi)有傳遞參數(shù)的功能的违霞,按普通的模式如果想要傳遞參數(shù),就只能將其寫(xiě)入url中瞬场,但是url有長(zhǎng)度限制买鸽,另外有些場(chǎng)景中我們想傳遞的參數(shù)還不想暴露在url里,這時(shí)就可以用falshMap來(lái)進(jìn)行傳遞了贯被,我們之需要在redirect之前將需要傳遞的參數(shù)寫(xiě)入OUTPUT_FLASH_MAP_ATTRIBUTE眼五,如下(這里使用了前面講到的RequestConetxtHolder):

    ((FlashMap)((ServletRequestAttributes)(RequestContextHolder.getRequestAttributes())).getRequest().getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name","張三豐");

?這樣在redirect之后的handle中spring就會(huì)自動(dòng)將其設(shè)置到model里(先設(shè)置到INPUT_FALSH_MAP_ATTRIBUTE屬性里,然后再放到model里)刃榨。當(dāng)然這樣操作還是有點(diǎn)麻煩弹砚,spring還給我們提供了更加簡(jiǎn)潔的操作方法,我們只需要在handler方法的參數(shù)中定義RedirectAttributes類(lèi)型的變量枢希,然后把需要保存的屬性設(shè)置到里面就行桌吃,之后的事情spring自動(dòng)完成。RedirectAttributes有兩種設(shè)置參數(shù)到方法addAttribute(key,value)和addFlashAttribute(key,value)苞轿,用第一個(gè)方法設(shè)置的參數(shù)會(huì)拼接到url中茅诱,第二個(gè)方法設(shè)置的參數(shù)就是用我們剛才所講的flashMap保存的逗物。比如,一個(gè)提交訂單的Controller可以這樣寫(xiě):

@RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submit(RedirectAttributes attr) throws IOException {
    ((FlashMap) ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest()
            .getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name", "張三豐");
    attr.addFlashAttribute("ordersId", "xxx");
    attr.addAttribute("local", "zh-cn");
    return "redirect:showorders";
}

@RequestMapping(value = "/showorders", method = RequestMethod.GET)
public String showOrders(Model model) throws IOException {
    // doSomething...
    return "orders";
}

?這里分別使用了三種方法來(lái)傳遞redirect參數(shù):

  • 使用前面講過(guò)的RequestContextHolder獲取到request瑟俭,并從其屬性中拿到outpurtFlashMap翎卓,然后將屬性放進(jìn)去,當(dāng)然request可以直接寫(xiě)到參數(shù)里讓Spring MVC給設(shè)置進(jìn)來(lái)摆寄,這里主要是為了讓大家看一下使用RequestContextHolder獲取request的方法失暴。
  • 通過(guò)傳入的attr參數(shù)的addFlashAttribute方法設(shè)置,這樣也可以保存到outputFlashMap中微饥,和第一種方法效果一樣逗扒。
  • 通過(guò)傳入的attr參數(shù)的addAttribute方法設(shè)置,這樣設(shè)置的參數(shù)不會(huì)保存到FlashMap而是會(huì)拼接到url中欠橘。

?從Request獲取outputFlashMap除了直接獲取DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE屬性矩肩,還可以使用RequestContextUtils來(lái)操作:RequestContextUtils.getOutputFlashMap(request),這樣也可以得到outputFlashMap,其實(shí)它內(nèi)部還是從Request的屬性獲取的肃续。
?當(dāng)用戶(hù)提交http://xxx/submit請(qǐng)求后瀏覽器地址欄會(huì)自動(dòng)跳轉(zhuǎn)到http://xxx/showorders?Local=zh-cn鏈接黍檩,而在showOrders的model里會(huì)存在["name","張三豐"]和["ordersId","xxx"]兩個(gè)屬性,而且對(duì)客戶(hù)端是透明的始锚,用戶(hù)并不知道刽酱。
?這就是flashMap的用法,inputFlashMap用于保存上次請(qǐng)求中轉(zhuǎn)發(fā)過(guò)來(lái)的屬性疼蛾,outputFlashMap用于保存本次請(qǐng)求需求轉(zhuǎn)發(fā)的屬性肛跌,F(xiàn)lashMapManager用于管理它們艺配,后面會(huì)詳細(xì)分析FlashMapManager察郁。
?這就是flashMap的用法,inputFlashMap用于保存上次請(qǐng)求中轉(zhuǎn)發(fā)過(guò)來(lái)的屬性转唉,outputFlashMap用于保存本次請(qǐng)求需要轉(zhuǎn)發(fā)的屬性皮钠,F(xiàn)lashMapManager用于管理它們,后面會(huì)詳細(xì)分析FlashMapManager赠法。
?doService就分析完了麦轰,在這里主要是對(duì)request設(shè)置了一些屬性,如果是include請(qǐng)求還會(huì)對(duì)request當(dāng)前的屬性做快照備份砖织,并在處理結(jié)束后恢復(fù)款侵。最后將請(qǐng)求轉(zhuǎn)發(fā)給doDispatch方法。
?doDispatch方法非常簡(jiǎn)單侧纯,從頂層設(shè)計(jì)了整個(gè)請(qǐng)求處理的過(guò)程新锈。doDispatch中最核心的代碼只要4句,它們的任務(wù)分別是:

  1. 根據(jù)request找到Handler眶熬;
  2. 根據(jù)Handler找到對(duì)應(yīng)的HandlerAdapter妹笆;
  3. 用HadnlerAdapter處理Handler块请;
  4. 調(diào)用processDispatchResultResult方法處理上面處理之后的結(jié)果(包含找到View并渲染輸出給用戶(hù))

?對(duì)應(yīng)的代碼如下:

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

?這里需要解釋三個(gè)概念:HandlerMapping、Handler和HandlerAdapter拳缠。這三個(gè)概念的準(zhǔn)確理解對(duì)于Spring MVC的學(xué)習(xí)非常重要墩新。如果對(duì)這三個(gè)概念理解得不夠透徹,將會(huì)嚴(yán)重影響對(duì)Spring MVC的理解窟坐。下面給大家解釋一下:
???HandlerMapping:是用來(lái)查找Handler的海渊,在Spring MVC中會(huì)處理很多請(qǐng)求,沒(méi)個(gè)請(qǐng)求都需要一個(gè)Handler來(lái)處理哲鸳,具體接收到一個(gè)請(qǐng)求后使用哪個(gè)Handler來(lái)處理呢切省?這就是HandlerMapping要做的事情。
???HandlerAdapter:很多人對(duì)這個(gè)的理解都不準(zhǔn)確帕胆,其實(shí)從名字上就可以看出它是一個(gè)Adapter朝捆,也就是適配器。因?yàn)镾pring MVC中的Handler可以是任意的形式懒豹,只要能處理請(qǐng)求就OK芙盘,但是Servlet需要的處理方法的結(jié)構(gòu)卻是固定的,都是以request和response為參數(shù)的方法(如doService方法)脸秽。怎么讓固定的Servlet處理方法調(diào)用靈活的Handler來(lái)進(jìn)行處理呢儒老?這就是HandlerAdapter要做的事情。
?通俗點(diǎn)的解釋就是Handler是用來(lái)干活的工具记餐,HandlerMapping用于根據(jù)需要干的活找到相應(yīng)的工具驮樊,HandlerAdapter是使用工具干活的人。比如片酝,Handler就像車(chē)床囚衔、銑床、電火花之類(lèi)的設(shè)備雕沿,HandlerMapping的作用是根據(jù)加工的需求選擇用什么設(shè)備進(jìn)行加工练湿,而HandlerAdapter是具體操作設(shè)備的工人,不同的設(shè)備需要不同的工人去加工审轮,車(chē)床需要車(chē)工肥哎,銑床需要銑工,如果讓車(chē)工使用銑床干活就可能出問(wèn)題疾渣,所以不同的Handler需要不同的HandlerAdapter去使用篡诽。我們都知道在干活的時(shí)候人是柔性最強(qiáng)、靈活度最高的榴捡,同時(shí)也是問(wèn)題最多的杈女、困難最多的。Spring MVC中也一樣,在九大組件中HandlerAdapter也是最復(fù)雜的碧信,所以在后面學(xué)習(xí)HandlerAdapter的時(shí)候要多留心赊琳。
?另外View和ViewResolver的原理與Handler和HandlerMapping的原理類(lèi)似。View是用來(lái)展示數(shù)據(jù)的砰碴,而ViewResolver用來(lái)查找View躏筏。通俗地講就是干完活后需要寫(xiě)報(bào)告的,寫(xiě)報(bào)告又需要模版(比如呈枉,是調(diào)查報(bào)告還是驗(yàn)收?qǐng)?bào)告或者是下一步工作的請(qǐng)示等)趁尼,View就是所需要的模版,模版就像公文里邊的格式猖辫,內(nèi)容就是Model里面的數(shù)據(jù)酥泞,ViewResolver就是用來(lái)選擇使用哪個(gè)模版的。
?現(xiàn)在再回過(guò)頭去看上面的四句代碼應(yīng)該就覺(jué)得很容易理解了啃憎,它們分別是:使用HandlerMapping找到干活Handler芝囤,找到使用Handler的HandlerAdapter,讓HandlerAdapter使用Handler干活辛萍,干完活后將結(jié)果寫(xiě)個(gè)報(bào)告交上去(通過(guò)View展示給用戶(hù))悯姊。

10.4 doDispatch結(jié)構(gòu)

?10.3節(jié)介紹了doDispatch做的4件事,不過(guò)只是整體介紹贩毕,本節(jié)詳細(xì)分析doDispatch內(nèi)部的結(jié)構(gòu)以及處理的流程悯许。先來(lái)看doDispatch的代碼:

@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
    ...
    
    /**
     * Process the actual dispatching to the handler.
     * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
     * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
     * to find the first that supports the handler class.
     * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
     * themselves to decide which methods are acceptable.
     * @param request current HTTP request
     * @param response current HTTP response
     * @throws Exception in case of any kind of processing failure
     */
    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 {
                //檢查是不是上傳請(qǐng)求
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                
                //根據(jù)request找的handler  
                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                
                //根據(jù)handler找到HandlerAdapter
                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                
                //處理GET、HEAD請(qǐng)求的Last-modified
                // 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;
                    }
                }
                //之下相應(yīng)Interceptor的preHandle
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                //HandlerAdapter使用Handler處理請(qǐng)求
                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                //如果需要異步處理辉阶,直接返回
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                //當(dāng)view為空的時(shí)(比如先壕,Handler返回值為void),根據(jù)request設(shè)置默認(rèn)view
                applyDefaultViewName(processedRequest, mv);
                //執(zhí)行相應(yīng)Interceptor的postHandle
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                //spring 4.3版本開(kāi)始谆甜,當(dāng)遇到Throwable異常垃僚,使用@ExceptionHandler拋出異常  
                // 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);
            }
            //處理返回結(jié)果。包括處理異常店印、渲染頁(yè)面冈在、發(fā)出完成通知觸發(fā)Interceptor的afterCompletion
            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 {
            //判斷是否執(zhí)行異步請(qǐng)求
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                //清除上傳請(qǐng)求資源
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }
    
    ...
}

?doDispatch大體可以分為兩部分:處理請(qǐng)求和渲染也面。開(kāi)頭部分先定義了幾個(gè)變量按摘,在后面要用到,如下:

  • HttpServletRequest processedRequest:實(shí)際處理時(shí)所用的request纫谅,如果不是上傳請(qǐng)求則直接使用接收到的request炫贤,否則封裝為上傳請(qǐng)求類(lèi)型的request。
  • HandlerExceptionChain mappedHandler:處理請(qǐng)求的處理器鏈路(包含處理和對(duì)應(yīng)到Interceptor)付秕。
  • boolean multipartRequestParsed:是不是上傳請(qǐng)求的標(biāo)志兰珍。
  • ModelAndView mv:封裝Model和View的容器,此變量在整個(gè)Spring MVC處理的過(guò)程中承擔(dān)著非常重要角色询吴,如果使用過(guò)Spring MVC就不會(huì)對(duì)ModelAndView陌生掠河。
  • Exception dispatchException:處理請(qǐng)求過(guò)程中拋出的異常亮元。需要注意的是它并不包含渲染過(guò)程拋出異常。

?doDispatch中首先檢查是不是上傳請(qǐng)求唠摹,如果是上傳請(qǐng)求爆捞,則將request轉(zhuǎn)換為MultipartHttpServletRequest,并將multipartRequestParsed標(biāo)志設(shè)置為true勾拉。其中使用到了MultipartResolver煮甥。
?然后通過(guò)getHandler方法獲取Handler處理器鏈,其中使用到了HandlerMapping藕赞,返回值為HandlerExceptionChain類(lèi)型成肘,其中包含著與當(dāng)前request相匹配的Interceptor和Handler。getHandler代碼如下:

@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
    ...

    /**
     * Return the HandlerExecutionChain for this request.
     * <p>Tries all handler mappings in order.
     * @param request current HTTP request
     * @return the HandlerExecutionChain, or {@code null} if no handler could be found
     */
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }
    
    ...
}

?方法結(jié)構(gòu)非常簡(jiǎn)單斧蜕,HandlerMapping在后面詳細(xì)講解双霍,HandlerExceptionChain的類(lèi)型類(lèi)似于前面Tomcat中講過(guò)的Pipeline,Interceptor和Handler相當(dāng)于哪里邊的Value和BaseValue批销,執(zhí)行時(shí)先依次執(zhí)行Interceptor的preHandle方法店煞,最后執(zhí)行Handler,返回的時(shí)候按鈕相反的順序執(zhí)行Interceptor的postHandle方法风钻。就好像要去一個(gè)地方顷蟀,Interceptor是要經(jīng)過(guò)的收費(fèi)站,Handler是目的地骡技,去的時(shí)候和返回的時(shí)候都要經(jīng)過(guò)加油站鸣个,但兩次所經(jīng)過(guò)的順序是相反的。
?接下來(lái)是處理GET布朦、HEAD請(qǐng)求的Last-Modifed囤萤。當(dāng)瀏覽器第一次跟服務(wù)器請(qǐng)求資源(GET、HEAD請(qǐng)求)時(shí)是趴,服務(wù)器在返回的請(qǐng)求頭里面會(huì)包含一個(gè)Last-Modified的屬性涛舍,代表本資源最后時(shí)什么時(shí)候修改的。在瀏覽器以后發(fā)送請(qǐng)求時(shí)會(huì)同時(shí)發(fā)送之前接受到的Last-Modified唆途,服務(wù)器接收到帶Last-Modified的請(qǐng)求后會(huì)用其值和自己實(shí)際資源帶最后修改時(shí)間做對(duì)比富雅,如果資源過(guò)期了則返回新的資源(同時(shí)返回新的Last-Modified),否則直接返回304狀態(tài)碼表示資源未過(guò)期肛搬,瀏覽器直接使用之前緩存的結(jié)果没佑。
?接下來(lái)依次調(diào)用相應(yīng)Interceptor的preHandle。
?處理完Interceptor的preHandler后就到了此方法最關(guān)鍵的地方---讓HandlerAdapter使用Handler處理請(qǐng)求温赔,Controller就是再這里執(zhí)行的蛤奢。這里主要使用了HandlerAdapter,具體內(nèi)容在后面詳細(xì)講解。
?Handler處理完請(qǐng)求后啤贩,如果需要異步處理待秃,則直接返回,如果不需要異步處理痹屹,當(dāng)view為空時(shí)(如Handler返回值為void)章郁,設(shè)置默認(rèn)view,然后執(zhí)行相應(yīng)Interceptor的postHandle痢掠。設(shè)置默認(rèn)view的過(guò)程中使用到了ViewNameTranslator驱犹。
?到這里請(qǐng)求處理的內(nèi)容就完成了,接下來(lái)使用processDispatchResult方法處理前面返回的結(jié)果足画,其中包括處理異常雄驹、渲染頁(yè)面觸發(fā)Interceptor的afterCompletion方法三部分內(nèi)容。
?我們先來(lái)說(shuō)一下doDispatch的異常處理結(jié)構(gòu)淹辞。doDispatch有兩層異常捕獲医舆,內(nèi)層是捕獲在對(duì)請(qǐng)求進(jìn)行處理的過(guò)程中拋出的異常,外層主要是再處理渲染頁(yè)面時(shí)拋出的象缀。內(nèi)層的一場(chǎng)蔬将,也就是執(zhí)行請(qǐng)求處理時(shí)的異常會(huì)設(shè)置到dispatchException變量,然后在processDispatchResult方法中進(jìn)行處理央星,外層則是處理processDispatchResult方法拋出的異常霞怀。processDispatchResult代碼如下:

@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
    ...

    /**
     * Handle the result of handler selection and handler invocation, which is
     * either a ModelAndView or an Exception to be resolved to a ModelAndView.
     */
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

        boolean errorView = false;
        //如果請(qǐng)求處理的過(guò)程中有異常拋出則處理異常
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }
            else {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }
        //渲染頁(yè)面
        // Did the handler return a view to render?
        if (mv != null && !mv.wasCleared()) {
            render(mv, request, 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");
            }
        }

        if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            //如果啟動(dòng)了異步處理則直接返回
            // Concurrent handling started during a forward
            return;
        }
        
        //發(fā)出請(qǐng)求處理完到通知,觸發(fā)Interceptor的afterCompletion
        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }

    ...
}

?可以看到processDispatchResult處理異常的方式其實(shí)就是將相應(yīng)的錯(cuò)誤頁(yè)面設(shè)置到View莉给,在其中的processHandlerException方法中用到了HandlerExceptionResolver毙石。
?渲染頁(yè)面具體在render方法中執(zhí)行,render中首先對(duì)response設(shè)置了Local颓遏,過(guò)程中使用到了LocaleResolver徐矩,然后判斷View如果是String類(lèi)型則調(diào)用resolveViewName方法使用ViewResolver得到實(shí)際的View,最后調(diào)用View的render方法對(duì)頁(yè)面進(jìn)行具體渲染叁幢,渲染的過(guò)程中使用到了ThemeResolver滤灯。
?最后通過(guò)mappedHandler的triggerAfterCompletion方法觸發(fā)Interceptor的afterCompletion方法,這里的Interceptor也是按反方向執(zhí)行的曼玩。到這里processDispatchResult方法的臨時(shí)資源鳞骤。
?再返回doDispacth方法中,在最后的finally中判斷請(qǐng)求是否啟動(dòng)了異步處理演训,如果啟動(dòng)了則調(diào)用相應(yīng)異步處理的攔截器弟孟,否則如果是上傳請(qǐng)求則刪除上傳請(qǐng)求過(guò)程中產(chǎn)生的臨時(shí)資源。
?doDispatch方法就分析完了样悟。可以看到Spring mvc的處理方式是先在頂層設(shè)計(jì)好整體結(jié)構(gòu),然后將具體的處理交給不同的組件具體去實(shí)現(xiàn)的窟她。doDispatch的流程圖如圖10-1所示陈症,中間是doDispatch的處理流程圖,左邊是Interceptor相關(guān)處理方法的調(diào)用位置震糖,右邊是doDispatch方法處理過(guò)程中所涉及的組件录肯。圖中上半部分的處理請(qǐng)求對(duì)應(yīng)著MVC中的Controller也就是C層,下半部分的processDispacthResult主要對(duì)應(yīng)了MVC中的View也就是V層吊说,M層也就是Model貫穿于整個(gè)過(guò)程中论咏。

doDispatch方法處理流程圖.png

?理解doDispacth的結(jié)構(gòu)之后在開(kāi)發(fā)過(guò)程中如果遇到問(wèn)題,就可以知道在哪部分出的問(wèn)題颁井,從而縮小查找范圍厅贪,有的放矢去解決。

10.5 小結(jié)

?本章整體分析了Spring MVC中請(qǐng)求處理的過(guò)程雅宾。首先對(duì)三個(gè)Servlet進(jìn)行了分析养涮,然后單獨(dú)分析了DispatcherServlet中的doDispatch方法。
?三個(gè)Servlet的處理過(guò)程大致功能如下:

  • HttpServletBean:沒(méi)有參與實(shí)際請(qǐng)求的處理眉抬。
  • FrameworkServlet:將不同類(lèi)型的請(qǐng)求合并到了processRequest方法統(tǒng)一處理贯吓,processRequest方法中做了三件事:
    • 調(diào)用了doService模版方法具體處理請(qǐng)求。
    • 將當(dāng)前請(qǐng)求的LocaleContext和ServletRequestAttributes在處理請(qǐng)求前設(shè)置到了LocaleContextHolder和RequestContextHolder蜀变,并在請(qǐng)求處理完成后恢復(fù)悄谐。
    • 請(qǐng)求處理完后發(fā)布了ServletRequestHandleEvent消息。
  • DispatcherServlet:doService方法給request設(shè)置了一些屬性并將請(qǐng)求交給doDispatch方法具體處理库北。

?DispatcherServlet中的doDispatch方法完成Spring MVC中請(qǐng)求處理過(guò)程的頂層設(shè)計(jì)爬舰,它使用DispatcherServlet中的九大組件完成了具體的請(qǐng)求處理。另外HandlerMapping贤惯、Handler和HandlerAdapter這三個(gè)概念的含義以及它們之間的關(guān)系也非常重要洼专。

結(jié)語(yǔ)

?終于寫(xiě)完了《詳解Spring MVC 上下》兩篇博文,一共一萬(wàn)字孵构,希望對(duì)大家有所幫助屁商,在面試的時(shí)候能夠震懾住面試,順利應(yīng)聘理想的職務(wù)颈墅。

《詳解Servlet》
《詳解Spring MVC:上》

如果需要給我修改意見(jiàn)的發(fā)送郵箱:erghjmncq6643981@163.com
資料參考:《看透Spring MVC-源代碼分析與實(shí)踐》
轉(zhuǎn)發(fā)博客蜡镶,請(qǐng)注明,謝謝恤筛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末官还,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子毒坛,更是在濱河造成了極大的恐慌望伦,老刑警劉巖林说,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異屯伞,居然都是意外死亡腿箩,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)劣摇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)珠移,“玉大人,你說(shuō)我怎么就攤上這事末融【澹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵勾习,是天一觀的道長(zhǎng)浓瞪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)语卤,這世上最難降的妖魔是什么追逮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮粹舵,結(jié)果婚禮上钮孵,老公的妹妹穿的比我還像新娘。我一直安慰自己眼滤,他們只是感情好巴席,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著诅需,像睡著了一般漾唉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上堰塌,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天赵刑,我揣著相機(jī)與錄音,去河邊找鬼场刑。 笑死般此,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的牵现。 我是一名探鬼主播铐懊,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瞎疼!你這毒婦竟也來(lái)了科乎?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贼急,失蹤者是張志新(化名)和其女友劉穎茅茂,沒(méi)想到半個(gè)月后捏萍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玉吁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年照弥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腻异。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片进副。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖悔常,靈堂內(nèi)的尸體忽然破棺而出影斑,到底是詐尸還是另有隱情,我是刑警寧澤机打,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布矫户,位于F島的核電站,受9級(jí)特大地震影響残邀,放射性物質(zhì)發(fā)生泄漏皆辽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一芥挣、第九天 我趴在偏房一處隱蔽的房頂上張望驱闷。 院中可真熱鬧,春花似錦空免、人聲如沸空另。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扼菠。三九已至,卻和暖如春坝咐,著一層夾襖步出監(jiān)牢的瞬間循榆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工墨坚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秧饮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓框杜,卻偏偏與公主長(zhǎng)得像浦楣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咪辱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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