Spring源碼分析之WebMVC

作者: 一字馬胡
轉(zhuǎn)載標(biāo)志 【2018-01-07】

更新日志

日期 更新內(nèi)容 備注
2018-01-07 創(chuàng)建分析文檔 Spring源碼分析系列文章(四)

導(dǎo)入

Spring源碼分析系列文章索引:

本文是系列文章的第四篇缝驳,內(nèi)容為Spring WebMVC模塊的源碼分析潮尝。主要分析WebMVC模塊的工作流程哨毁,依然只分析主干流程,不會(huì)涉及太多的細(xì)節(jié)茄蚯。MVC是一種將Web層進(jìn)行解耦的架構(gòu)模式饲帅,MVC即Model、View耐薯、Controller顽分,Model即數(shù)據(jù)模型徐许,View即視圖,Controller即處理器卒蘸,知道了MVC的大概原理雌隅,就可以開始進(jìn)行Spring MVC的源碼分析了,Spring MVC是MVC架構(gòu)的一種優(yōu)秀實(shí)現(xiàn)缸沃,能高效的進(jìn)行Web模塊的開發(fā)工作恰起。

在進(jìn)行實(shí)際的Spring MVC源碼分析之前,大概先來猜測一下整個(gè)流程中的關(guān)鍵步驟趾牧,然后對照著猜測來分析Spring MVC中的具體實(shí)現(xiàn)检盼。

  • 首要的一點(diǎn)是如何將bean解析并加載到容器中來。因?yàn)樵赟pring MVC開發(fā)中武氓,我們不需要也沒有權(quán)限寫一個(gè)main方法梯皿,然后使用ApplicationContext來加載xml文件的環(huán)節(jié)(是否也可以這樣呢?但是需要放在什么位置來加載xml呢县恕?使用什么來加載xml呢东羹?),所以在WebMVC中首先要解決的一個(gè)問題就是使用一個(gè)特殊的組件來觸發(fā)Spring的bean解析-bean加載這個(gè)流程忠烛。
  • 在把Spring bean加載成功之后属提,現(xiàn)在,我們可以正常使用我們在Spring配置文件中配置的bean了美尸,對于Web應(yīng)用來說冤议,使用的協(xié)議一般為Http/Https,所以接下來需要考慮的一個(gè)問題是师坎,在Spring MVC中是如何處理客戶端請求的恕酸。客戶端的請求應(yīng)該是一個(gè)http請求胯陋,而達(dá)到Spring MVC之后需要做的事情應(yīng)該是找到合適的Controller蕊温,并且找到Controller中的具體可以處理該請求的Handler,讓Handler來處理請求遏乔,并且獲取到結(jié)果之后將結(jié)果傳遞到View解析器义矛,View解析器會(huì)根據(jù)合適的視圖對數(shù)據(jù)進(jìn)行渲染,然后返回給客戶端去盟萨。

第一步看起來比較簡單凉翻,畢竟我們需要的只是在合適的時(shí)候引導(dǎo)Spring來加載xml文件來解析bean并且加載解析的bean,較為復(fù)雜和核心的功能應(yīng)該是第二步捻激,需要做的事情看起來非常多制轰,首先要攔截請求前计,并且將請求封裝成Spring MVC可以處理的bean,然后需要根據(jù)請求來選擇合適的Controller艇挨,并且將合適的Handler交給攔截器進(jìn)行請求處理残炮,處理完了還需要將數(shù)據(jù)交付給視圖渲染組件來返回合適的試圖。

所以就目前來說缩滨,在第二步势就,有幾個(gè)問題需要得到解決:

  1. Controller看起來和普通的bean是不一樣的,因?yàn)槠胀ǖ腷ean不涉及Handler脉漏,而Controller涉及到Handler苞冯,并且可能一個(gè)Controller包含了多個(gè)Handler,所以看起來Controller需要特殊對待侧巨,就解析Controller來說舅锄,需要結(jié)合Controller代碼來進(jìn)行解析,將所有Controller支持的Handler和對應(yīng)的Url解析好司忱,然后在請求到達(dá)的時(shí)候皇忿,只需要和解析好的這些Url和Handler進(jìn)行匹配就可以了。
  2. Controller的Handler解析好了坦仍,那是怎么匹配具體的請求的呢鳍烁?這是另外一個(gè)需要考慮的問題,一個(gè)請求肯定會(huì)帶著一個(gè)url繁扎,怎么為這個(gè)url找到合適的Handler是處理請求的關(guān)鍵一步幔荒。
  3. 匹配好了Handler之后,Handler是怎么處理請求的呢梳玫?也就是怎么交給具體的Handler的呢爹梁?處理完成的數(shù)據(jù)會(huì)流轉(zhuǎn)到哪里呢?

現(xiàn)在想起來并不會(huì)太全面提澎,很容易忽略細(xì)節(jié)姚垃,但是大概就應(yīng)該是這樣,下面就帶著這些問題來分析Spring WebMVC的源碼盼忌。

Spring WebMVC模塊解析

首先是第一個(gè)問題积糯,怎么觸發(fā)Spring中的bean的解析-bean的加載那一套流程。在Spring WebMVC項(xiàng)目中都需要配置一個(gè)web.xml文件碴犬,這個(gè)文件配置一些相關(guān)Web的配置項(xiàng),下面是一個(gè)關(guān)鍵配置梆暮,與觸發(fā)Spring bean解析密切相關(guān):


    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

context-param標(biāo)簽用于進(jìn)行一些key-value類型的配置項(xiàng)設(shè)置服协,key是param-name,Value就是param-value啦粹,上面的配置中配置了一個(gè)key為contextConfigLocation的配置項(xiàng)偿荷,代表Spring bean解析bean的xml文件來源窘游,從value可以看出,Spring會(huì)加載classpath:applicationContext.xml這些文件來進(jìn)行bean的解析跳纳。

接著是一個(gè)listener標(biāo)簽忍饰,看起來是一個(gè)監(jiān)聽器,所謂監(jiān)聽器寺庄,就是會(huì)監(jiān)聽一些事件艾蓝,當(dāng)某些事件發(fā)生的時(shí)候就會(huì)做一些事情的組件,而listener-class標(biāo)簽設(shè)定了具體的監(jiān)聽器類斗塘。在上面的設(shè)置中設(shè)置了ContextLoaderListener這個(gè)類赢织,看起來就是這個(gè)類來觸發(fā)Spring 進(jìn)行bean的加載,而上面的context-param配置的就是Spring 加載bean掃描的xml文件馍盟,下面來具體分析一下整個(gè)流程于置。

ContextLoaderListener 實(shí)現(xiàn)了 ServletContextListener接口,ServletContextListener 有一個(gè)關(guān)鍵的方法是contextInitialized贞岭,根據(jù)注釋八毯,這個(gè)方法會(huì)在web應(yīng)用初始化的時(shí)候調(diào)用,所以也就是在Web應(yīng)用最開始的地方會(huì)觸發(fā)這個(gè)方法瞄桨,具體聯(lián)系ContextLoaderListener话速,就是會(huì)在這個(gè)時(shí)候進(jìn)行bean的加載流程,下面來具體分析一下ContextLoaderListener中contextInitialized這個(gè)方法的實(shí)現(xiàn):


    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

關(guān)鍵的流程流轉(zhuǎn)到了initWebApplicationContext這個(gè)方法中來了讲婚,下面來分析一下initWebApplicationContext這個(gè)方法中的關(guān)鍵代碼:


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

上面的代碼截取自initWebApplicationContext方法尿孔,這個(gè)方法實(shí)現(xiàn)的功能就是觸發(fā)Spring的bean的加載流程,但是這只是觸發(fā)的開始筹麸,來分析一下上面的代碼活合,首先是createWebApplicationContext方法,會(huì)根據(jù)servletContext來create一個(gè)WebApplicationContext物赶,下面是這個(gè)方法的具體實(shí)現(xiàn):


    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

經(jīng)過上面的步驟之后白指,就會(huì)走到關(guān)鍵的方法configureAndRefreshWebApplicationContext,這個(gè)方法很關(guān)鍵酵紫,它引導(dǎo)web應(yīng)用開始進(jìn)行bean的加載操作告嘲,下面來看一下這個(gè)方法內(nèi)部的實(shí)現(xiàn):

其中的configLocationParam就是我們在web.xml中配置的那個(gè)參數(shù),是Spring掃描bean的路徑奖地,配置好路徑之后橄唬,就和我們自己寫加載bean的流程是一樣的了,只是這里webMVC會(huì)自動(dòng)進(jìn)行這些步驟参歹,看到最后的wac.refresh()仰楚,就可以確定,Spring要開始進(jìn)行xml的加載,并且進(jìn)行bean的解析僧界、加載等流程了侨嘀,關(guān)于這些步驟已經(jīng)在前面的文章中分析過,在此不再贅述捂襟。

DispatcherServlet

DispatcherServlet是Spring MVC中的核心組件咬腕,它接收請求,并且為請求找到合適的Handler進(jìn)行處理葬荷,DispatcherServlet也是本文分析的重點(diǎn)內(nèi)容涨共,下面首先來看一下再Spring MVC應(yīng)用中web.xml中關(guān)于DispatcherServlet的配置:



    <servlet>
        <servlet-name>Spring-MVC-API-Servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:springmvc-servlet.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Spring-MVC-API-Servlet</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>

就像上面看起來的一樣,每一個(gè)servlet都需要配置一個(gè)ervlet-mapping闯狱,用來將相應(yīng)的請求交給對應(yīng)的DispatcherServlet進(jìn)行處理煞赢。在上面的配置中,配置了一個(gè)servlet名字叫做Spring-MVC-API-Servlet哄孤,指定為org.springframework.web.servlet.DispatcherServlet這個(gè)類照筑,并且配置了加載Controller的xml文件路徑為classpath*:springmvc-servlet.xml,除此之外瘦陈,為該servlet配置了mapping凝危,將所有以/api開頭的請求都路由到該DispatcherServlet進(jìn)行處理。一個(gè)Web 應(yīng)用可以配置多個(gè)DispatcherServlet來處理不同的資源類型晨逝,視具體情況來使用蛾默,只需要記住,每一個(gè)DispatcherServlet都需要配置配套的mapping就可以了捉貌。

有了DispatcherServlet配置支鸡,現(xiàn)在來分析一下DispatcherServlet的具體細(xì)節(jié),在第一步觸發(fā)Spring bean的加載流程哪里趁窃,結(jié)束之后并不會(huì)包含Controller牧挣,所以Controller需要特殊解析,因?yàn)樯婕暗紿andler的解析問題醒陆,所以可以理解需要特殊解析瀑构。下面是DispatcherServlet的類圖,可以看到DispatcherServlet的實(shí)現(xiàn)是比較復(fù)雜的:

首先關(guān)注DispatcherServlet實(shí)現(xiàn)了Servlet接口刨摩,Servlet接口有一個(gè)重要的方法init寺晌,這個(gè)方法應(yīng)該是會(huì)在實(shí)例化了Servlet之后調(diào)用,具體的實(shí)現(xiàn)是在HttpServletBean的澡刹,下面是它的實(shí)現(xiàn):


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

        // 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();

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

其中有一個(gè)方法值得注意:initServletBean呻征,具體的實(shí)現(xiàn)在FrameworkServlet:


    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
        }

        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
        
    }

這里面初始化的就是我們在web.cml中配置的一個(gè)DispatcherServlet,每一個(gè)DispatcherServlet都會(huì)進(jìn)行一次罢浇,首先需要注意的一個(gè)方法是initWebApplicationContext這個(gè)方法陆赋,然后是initFrameworkServlet這個(gè)方法边篮,首先來看一下前面的那個(gè)方法的具體實(shí)現(xiàn)內(nèi)容。


        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                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 -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }

上面是第一個(gè)試圖找到一個(gè)webApplicationContext的第一個(gè)分支奏甫,如果webApplicationContext在構(gòu)造函數(shù)中被帶賦值,那么就會(huì)走到該分支中來凌受,這個(gè)分支中需要關(guān)注的一個(gè)方法是configureAndRefreshWebApplicationContext阵子,這個(gè)方法做什么的呢?下面是該方法的具體實(shí)現(xià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...
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(getServletContext().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);
        wac.refresh();
    }

最后的wac.refresh()代表需要重新走一次Spring bean加載的流程胜蛉。下面是第二個(gè)試圖找到一個(gè)webApplicationContext的第二個(gè)分支:


        if (wac == null) {
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }

下面是第三個(gè)分支挠进,一般情況下會(huì)走到這個(gè)分支中來:


        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            wac = createWebApplicationContext(rootContext);
        }

    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        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");
        }
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        wac.setConfigLocation(getContextConfigLocation());

        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }
    

主要是設(shè)定了掃描路徑,然后就調(diào)用了configureAndRefreshWebApplicationContext方法來開始進(jìn)行bean的加載流程誊册,這個(gè)方法在上面已經(jīng)提及领突,在此不再贅述。

接著是一個(gè)關(guān)鍵的方法onRefresh:


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

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        
        //初始化andlerMappings
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

這其中初始化了很多內(nèi)容案怯,比如MultipartResolver君旦、hemeResolver等,但是目前我比較關(guān)心的是HandlerMappings的初始化嘲碱,下面就主要來分析這個(gè)initHandlerMappings這個(gè)方法的實(shí)現(xiàn)細(xì)節(jié)金砍。


private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

上面這個(gè)方法大概的意思就是加載系統(tǒng)設(shè)置的HandlerMappings,可以在webMVC模塊中的Resources中看到有一個(gè)文件叫做DispatcherServlet.properties麦锯,可以在其中找到下面的內(nèi)容:


org.springframework.web.servlet.HandlerMapping=
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

也就是說恕稠,如果沒有設(shè)置自己的HandlerMappings的話,Spring就會(huì)去加載這兩個(gè)HandlerMappings扶欣,本文主要分析后者RequestMappingHandlerMapping鹅巍。

到目前為止,總結(jié)一下現(xiàn)在的上下文料祠,首先骆捧,我們已經(jīng)知道了Spring MVC是什么時(shí)候以及怎么樣觸發(fā)Bean的加載流程的,這一步貌似和Spring MVC關(guān)系不大术陶,但是卻很重要凑懂,接著,了解了web.xml中關(guān)于servlet的配置準(zhǔn)則梧宫,以及配置的意義接谨,然后對servlet進(jìn)行了初始化,并且最終觸發(fā)了一系列的初始化塘匣,包括HandlerMappings脓豪。至此,貌似可以接受請求了忌卤,也就是說Spring WebMVC的分析已經(jīng)走了一半了扫夜,接下來的一半內(nèi)容就是如何處理請求了,這就得和Servlet的生命周期配合起來理解分析了,并且會(huì)涉及到Servlet將請求交給合適的Controller的合適的Handler的過程笤闯。下面來逐步分析一下堕阔。

DispatcherServlet在實(shí)現(xiàn)上繼承了HttpServlet,而HttpServlet提高了大量的方法來進(jìn)行請求的處理颗味,比如doGet超陆、doPut等,而HttpServlet中的service方法就是一個(gè)dispatch浦马,會(huì)解析請求时呀,然后根據(jù)不同的請求方法來調(diào)用不同的doXXX方法,我們主要關(guān)注doGet和doPut方法即可晶默。需要注意的是谨娜,service方法在FrameworkServlet類中被重寫了,具體實(shí)現(xiàn)如下:


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

一般情況下會(huì)走到else分支中磺陡,然后調(diào)用了super的service方法趴梢,下面是該方法的實(shí)現(xiàn)內(nèi)容:


protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            
        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

可以看到,service方法就是解析請求币他,然后根據(jù)不同的請求類型交給不同的方法來處理垢油,比如GET類型的請求就會(huì)交給doGet方法來進(jìn)行處理,下面主要關(guān)注doGet這個(gè)方法的接下來的流程圆丹,其余的方法分析類似滩愁,就不再贅述了。

doGet方法在FrameworkServlet類中重寫了辫封,所以會(huì)走到FrameworkServlet類中的doGet方法中來硝枉,下面是該方法的具體實(shí)現(xiàn):


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

        processRequest(request, response);
    }

接著走到了processRequest方法內(nèi)部,下面是該方法的主要代碼:


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);
        
        doService(request, response);       
        
}       

這個(gè)方法將對Request和Response做一些修飾倦微,然后就會(huì)走到doService這個(gè)方法妻味。下面是doService方法的具體細(xì)節(jié):


    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.
        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("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()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }

看起來這個(gè)方法也還沒開始真正處理請求,而會(huì)繼續(xù)修飾Request欣福,然后交給doDispatch這個(gè)方法來做责球,下面是doDispatch方法的具體細(xì)節(jié):


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

看起來這個(gè)方法就是真正處理請求的方法了,下面詳細(xì)分析一下這個(gè)方法的實(shí)現(xiàn)內(nèi)容拓劝。

首先對request做了一些處理雏逾,然后會(huì)調(diào)用getHandler來獲取一個(gè)可以處理該請求的Handler,這個(gè)方法是關(guān)鍵郑临,需要詳細(xì)分析一下栖博。


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

這個(gè)方法內(nèi)部會(huì)進(jìn)行遍歷所有已加載的handlerMappings,而handlerMappings的加載在上文中已經(jīng)提到過厢洞,getHandler方法會(huì)詢問所有加載的handlerMappings仇让,看看到底哪個(gè)handlerMapping可以處理典奉。通過調(diào)用HandlerMapping的getHandler方法來進(jìn)行判斷是否這個(gè)Handler可以處理當(dāng)前請求。下面以RequestMappingHandlerMapping為例來分析接下來的具體流程丧叽∥谰粒可以在AbstractHandlerMapping類中找到getHandler這個(gè)方法:


    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }

該方法的關(guān)鍵是第一個(gè)方法調(diào)用getHandlerInternal,下面來分析一下getHandlerInternal這個(gè)方法的實(shí)現(xiàn)細(xì)節(jié)踊淳『П剩可以在AbstractHandlerMethodMapping中找到getHandlerInternal這個(gè)方法的具體實(shí)現(xiàn):


    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        if (logger.isDebugEnabled()) {
            logger.debug("Looking up handler method for path " + lookupPath);
        }
        this.mappingRegistry.acquireReadLock();
        try {
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            if (logger.isDebugEnabled()) {
                if (handlerMethod != null) {
                    logger.debug("Returning handler method [" + handlerMethod + "]");
                }
                else {
                    logger.debug("Did not find handler method for [" + lookupPath + "]");
                }
            }
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

這里面的第一個(gè)需要注意的方法是getUrlPathHelper().getLookupPathForRequest,該方法會(huì)根據(jù)請求解析出具體的用于匹配Handler的url嚣崭,這是一個(gè)很關(guān)鍵的步驟,尋找合適的Handler就是根據(jù)url來進(jìn)行的懦傍。下面是該方法的具體實(shí)現(xiàn)內(nèi)容:


    public String getLookupPathForRequest(HttpServletRequest request) {
        // Always use full path within current servlet context?
        if (this.alwaysUseFullPath) {
            return getPathWithinApplication(request);
        }
        // Else, use path within current servlet mapping if applicable
        String rest = getPathWithinServletMapping(request);
        if (!"".equals(rest)) {
            return rest;
        }
        else {
            return getPathWithinApplication(request);
        }
    }

這個(gè)方法里面需要注意的是getPathWithinServletMapping這個(gè)方法的調(diào)用雹舀,這就是具體的對請求的url的處理。下面是該方法的具體實(shí)現(xiàn)細(xì)節(jié):


    public String getPathWithinServletMapping(HttpServletRequest request) {
        String pathWithinApp = getPathWithinApplication(request);
        String servletPath = getServletPath(request);
        String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
        String path;

        // if the app container sanitized the servletPath, check against the sanitized version
        if (servletPath.indexOf(sanitizedPathWithinApp) != -1) {
            path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
        }
        else {
            path = getRemainingPath(pathWithinApp, servletPath, false);
        }

        if (path != null) {
            // Normal case: URI contains servlet path.
            return path;
        }
        else {
            // Special case: URI is different from servlet path.
            String pathInfo = request.getPathInfo();
            if (pathInfo != null) {
                // Use path info if available. Indicates index page within a servlet mapping?
                // e.g. with index page: URI="/", servletPath="/index.html"
                return pathInfo;
            }
            if (!this.urlDecode) {
                // No path info... (not mapped by prefix, nor by extension, nor "/*")
                // For the default servlet mapping (i.e. "/"), urlDecode=false can
                // cause issues since getServletPath() returns a decoded path.
                // If decoding pathWithinApp yields a match just use pathWithinApp.
                path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
                if (path != null) {
                    return pathWithinApp;
                }
            }
            // Otherwise, use the full servlet path.
            return servletPath;
        }
    }

getPathWithinApplication這個(gè)方法會(huì)解析好一個(gè)請求的url(純潔的url粗俱,比如 /api/user/1310561):


    public String getPathWithinApplication(HttpServletRequest request) {
        String contextPath = getContextPath(request);
        String requestUri = getRequestUri(request);
        String path = getRemainingPath(requestUri, contextPath, true);
        if (path != null) {
            // Normal case: URI contains context path.
            return (StringUtils.hasText(path) ? path : "/");
        }
        else {
            return requestUri;
        }
    }

接著说榆,getServletPath這個(gè)方法會(huì)返回web.xml中配置的路由路徑:


    public String getServletPath(HttpServletRequest request) {
        String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE);
        if (servletPath == null) {
            servletPath = request.getServletPath();
        }
        if (servletPath.length() > 1 && servletPath.endsWith("/") && shouldRemoveTrailingServletPathSlash(request)) {
            // On WebSphere, in non-compliant mode, for a "/foo/" case that would be "/foo"
            // on all other servlet containers: removing trailing slash, proceeding with
            // that remaining slash as final lookup path...
            servletPath = servletPath.substring(0, servletPath.length() - 1);
        }
        return servletPath;
    }

getRemainingPath這個(gè)方法大概是將url中的前綴去掉,所謂前綴就是web.xml中配置的路由寸认,這樣才能去匹配Controller中的Handler對吧签财?現(xiàn)在回到getHandlerInternal這個(gè)方法,現(xiàn)在可以拿到lookupPath了偏塞,那接下來就可以根據(jù)lookupPath來匹配Controller的Handler了吧唱蒸?接著往下分析。接著一個(gè)比較關(guān)鍵的方法是lookupHandlerMethod灸叼,下面來分析一下這個(gè)方法的實(shí)現(xiàn):


    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<Match>();
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }

        if (!matches.isEmpty()) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            Collections.sort(matches, comparator);
            if (logger.isTraceEnabled()) {
                logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
                        lookupPath + "] : " + matches);
            }
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                            request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
                }
            }
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

這里面需要關(guān)注的一個(gè)方法是addMatchingMappings神汹,用于添加匹配的Handler:


    private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
        for (T mapping : mappings) {
            T match = getMatchingMapping(mapping, request);
            if (match != null) {
                matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
            }
        }
    }

獲取到所有匹配的Handler之后需要挑選一個(gè)最合適的Handler進(jìn)行請求的處理,lookupHandlerMethod方法中接下來的代碼實(shí)現(xiàn)的功能就是這些古今,下面回到doDispatch方法屁魏,接著走接下來的流程,現(xiàn)在捉腥,我們已經(jīng)獲取到了合適的Handler氓拼,下面,就是進(jìn)行Handler的訪問來處理請求了抵碟。
關(guān)鍵代碼是:ha.handle(processedRequest, response, mappedHandler.getHandler())桃漾,具體的handle方法實(shí)現(xiàn)在AbstractHandlerMethodAdapter類中,具體實(shí)現(xiàn)細(xì)節(jié)如下:


    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return handleInternal(request, response, (HandlerMethod) handler);
    }

protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ModelAndView mav;
        checkRequest(request);

        // Execute invokeHandlerMethod in synchronized block if required.
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // No HttpSession available -> no mutex necessary
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No synchronization on session demanded at all...
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }

        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            }
            else {
                prepareResponse(response);
            }
        }

        return mav;
    }

handleInternal就是我們希望看到的方法拟逮,這個(gè)方法做的事情就是執(zhí)行Controller中根據(jù)url挑選出來的Handler呈队,并且將Handler的處理結(jié)果進(jìn)行合適的view渲染的過程,關(guān)鍵的方法是invokeHandlerMethod:


    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);

            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                if (logger.isDebugEnabled()) {
                    logger.debug("Found concurrent result value [" + result + "]");
                }
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }

            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }

上面的方法的關(guān)鍵是invocableMethod.invokeAndHandle唱歧,下面是關(guān)鍵的方法調(diào)用代碼:


public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            StringBuilder sb = new StringBuilder("Invoking [");
            sb.append(getBeanType().getSimpleName()).append(".");
            sb.append(getMethod().getName()).append("] method with arguments ");
            sb.append(Arrays.asList(args));
            logger.trace(sb.toString());
        }
        Object returnValue = doInvoke(args);
        if (logger.isTraceEnabled()) {
            logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
        }
        return returnValue;
    }

doInvoke方法就是實(shí)際訪問Handler的實(shí)現(xiàn)宪摧,到此粒竖,Controller中的Handler方法已經(jīng)執(zhí)行完成了,接著會(huì)調(diào)用getModelAndView來進(jìn)行試圖渲染几于,這一部分的內(nèi)容就不再分析了蕊苗,未來找機(jī)會(huì)再進(jìn)行詳細(xì)分析。

至此沿彭,我們居然已經(jīng)分析完了一個(gè)請求的處理流程朽砰,包括請求的解析,url匹配Handler喉刘,已經(jīng)Controller中Handler的執(zhí)行等內(nèi)容瞧柔,但是好像還缺點(diǎn)什么,那就是我們在進(jìn)行用url來匹配handler的時(shí)候睦裳,貌似沒有解析Controller類的流程造锅,但是可以肯定的是這個(gè)流程肯定是存在的,那是否這個(gè)流程在處理請求之前就完成了呢廉邑?現(xiàn)在來挖掘一下這部分的內(nèi)容哥蔚,當(dāng)我們找到并且分析了這部分的內(nèi)容之后,整個(gè)流程就算是走通了蛛蒙。

在AbstractHandlerMethodMapping類中有一個(gè)方法特別關(guān)鍵糙箍,那就是afterPropertiesSet:


    public void afterPropertiesSet() {
        initHandlerMethods();
    }

protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
    

AbstractHandlerMethodMapping實(shí)現(xiàn)了InitializingBean接口,所以在bean進(jìn)行了初始化之后會(huì)調(diào)用該afterPropertiesSet做一些事情牵祟,下面來具體分析一下initHandlerMethods這個(gè)方法的細(xì)節(jié)深夯,看樣子是開始初始化Handler的過程。首先獲取了所有的beanName诺苹,然后會(huì)對每一個(gè)bean進(jìn)行處理塌西,使用etApplicationContext().getType方法來獲取bean的類型,然后使用isHandler來判斷一個(gè)bean是否是Handler類型的bean筝尾,如果是的話就會(huì)調(diào)用detectHandlerMethods捡需,下面是isHandler方法的具體實(shí)現(xiàn):


    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

關(guān)于如何快速判斷一個(gè)類是否具有注解可以參考文章Java如何快速獲取類附帶的注解,只要注解中有 Controller或者RequestMapping就代表該類含有Handler筹淫,所以就得去detect站辉,現(xiàn)在回到initHandlerMethods方法,接著看一下detectHandlerMethods方法的具體實(shí)現(xiàn)损姜。


    protected void detectHandlerMethods(final Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                getApplicationContext().getType((String) handler) : handler.getClass());
        final Class<?> userType = ClassUtils.getUserClass(handlerType);

        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                new MethodIntrospector.MetadataLookup<T>() {
                    @Override
                    public T inspect(Method method) {
                        return getMappingForMethod(method, userType);
                    }
                });

        if (logger.isDebugEnabled()) {
            logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
        }
        for (Map.Entry<Method, T> entry : methods.entrySet()) {
            Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
            T mapping = entry.getValue();
            registerHandlerMethod(handler, invocableMethod, mapping);
        }
    }

主要看registerHandlerMethod這個(gè)方法饰剥,就是注冊Handler的實(shí)現(xiàn):


    protected void registerHandlerMethod(Object handler, Method method, T mapping) {
        this.mappingRegistry.register(mapping, handler, method);
    }

public void register(T mapping, Object handler, Method method) {
            this.readWriteLock.writeLock().lock();
            try {
                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                assertUniqueMethodMapping(handlerMethod, mapping);

                if (logger.isInfoEnabled()) {
                    logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
                }
                this.mappingLookup.put(mapping, handlerMethod);

                List<String> directUrls = getDirectUrls(mapping);
                for (String url : directUrls) {
                    this.urlLookup.add(url, mapping);
                }

                String name = null;
                if (getNamingStrategy() != null) {
                    name = getNamingStrategy().getName(handlerMethod, mapping);
                    addMappingName(name, handlerMethod);
                }

                CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                if (corsConfig != null) {
                    this.corsLookup.put(handlerMethod, corsConfig);
                }

                this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
            }
            finally {
                this.readWriteLock.writeLock().unlock();
            }
        }

register方法實(shí)現(xiàn)了handler的注冊,解析好了之后當(dāng)然要保存起來啊摧阅,否則后面怎么忍亍?所以在此注冊之后棒卷,就可以在用url匹配handler的時(shí)候使用了顾孽。

至此祝钢,整條鏈路就走通了,從Spring bean加載流程的觸發(fā)若厚,到web.xml的配置以及準(zhǔn)則等細(xì)節(jié)拦英,再到實(shí)際請求的處理流程,最后發(fā)現(xiàn)在處理請求的時(shí)候使用到的handler還沒有分析解析流程测秸,所以最后分析了MVC中Controller的handler解析流程疤估,在這一步保存解析好保存起來之后,后面處理請求的時(shí)候就可以用來匹配具體的url以及其他的匹配項(xiàng)了霎冯,最終一個(gè)請求可以得到處理铃拇。當(dāng)然,本文并沒有涉及到view渲染的分析沈撞,因?yàn)樵诤芏鄷r(shí)候慷荔,我們直接將model寫到了response中去了,而不是返回一個(gè)視圖关串,而且渲染視圖的流程簡單但是較為繁瑣,基于這些原因就不再分析了监徘。本文涉及的大部分細(xì)節(jié)內(nèi)容會(huì)不斷進(jìn)行學(xué)習(xí)補(bǔ)充晋修,但是主干流程應(yīng)該就像本文分析的一樣,并且這些都是可以猜出來的凰盔,只是Spring MVC的實(shí)現(xiàn)由他自己的優(yōu)秀的地方墓卦,而正是由于具備優(yōu)秀的特性,才值得不斷學(xué)習(xí)户敬,發(fā)現(xiàn)其中的優(yōu)秀落剪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尿庐,隨后出現(xiàn)的幾起案子忠怖,更是在濱河造成了極大的恐慌,老刑警劉巖抄瑟,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凡泣,死亡現(xiàn)場離奇詭異,居然都是意外死亡皮假,警方通過查閱死者的電腦和手機(jī)鞋拟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惹资,“玉大人贺纲,你說我怎么就攤上這事⊥什猓” “怎么了猴誊?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵潦刃,是天一觀的道長。 經(jīng)常有香客問我稠肘,道長福铅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任项阴,我火速辦了婚禮滑黔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘环揽。我一直安慰自己略荡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布歉胶。 她就那樣靜靜地躺著汛兜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪通今。 梳的紋絲不亂的頭發(fā)上粥谬,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音辫塌,去河邊找鬼漏策。 笑死,一個(gè)胖子當(dāng)著我的面吹牛臼氨,可吹牛的內(nèi)容都是我干的掺喻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼储矩,長吁一口氣:“原來是場噩夢啊……” “哼感耙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起持隧,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤即硼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后屡拨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谦絮,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年洁仗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了层皱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赠潦,死狀恐怖叫胖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情她奥,我是刑警寧澤瓮增,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布怎棱,位于F島的核電站,受9級(jí)特大地震影響绷跑,放射性物質(zhì)發(fā)生泄漏拳恋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一砸捏、第九天 我趴在偏房一處隱蔽的房頂上張望谬运。 院中可真熱鬧,春花似錦垦藏、人聲如沸梆暖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轰驳。三九已至,卻和暖如春弟灼,著一層夾襖步出監(jiān)牢的瞬間级解,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工田绑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勤哗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疹尾。 傳聞我的和親對象是個(gè)殘疾皇子秘豹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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

  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,430評(píng)論 1 92
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理诱咏,服務(wù)發(fā)現(xiàn)苔可,斷路器,智...
    卡卡羅2017閱讀 134,693評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,838評(píng)論 6 342
  • 1.1 spring IoC容器和beans的簡介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器袋狞,...
    simoscode閱讀 6,721評(píng)論 2 22
  • 最近大家都忙著早起早起早睡早睡焚辅。每天的的定點(diǎn)視頻也沒以前那么拖拉了,即使每次說再見好夢都說得很干脆但心里總是萬分的...
    Ben柒七閱讀 135評(píng)論 0 0