Spring MVC源碼解讀二

接上一篇:Spring MVC源碼解讀一

  • DispatchServlet:
    源碼位置:

DispatcherServlet是前置控制器喧锦,配置在web.xml文件中的涧狮。攔截匹配的請求洼裤,Servlet攔截匹配規(guī)則要自已定義蓬坡,把攔截下來的請求骏啰,依據(jù)相應(yīng)的規(guī)則分發(fā)到目標(biāo)Controller來處理离斩,是配置spring MVC的第一步

類繼承關(guān)系:



時(shí)序圖:


  • servlet初始化鏈路:
    通過時(shí)序圖可以看到银舱,HttpServletBean中調(diào)用了initServletBean()方法,通過源代碼可以看到是在HttpServletBean的init()中調(diào)用了initServletBean()
    HttpServletBean的init()方法源碼如下捐腿,重寫了HttpServlet的init()方法:
    /**
     * Map config parameters onto bean properties of this servlet, and
     * invoke subclass initialization.
     * @throws ServletException if bean properties are invalid (or required
     * properties are missing), or if subclass initialization fails.
     */
    @Override
    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) {
            if (logger.isErrorEnabled()) {
                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");
        }
    }

問題: 1、HttpServlet的init()是什么時(shí)候調(diào)用的柿顶? 解答: 在前面解讀spring mvc在web.xml中的load-on-startup配置時(shí)已經(jīng)回答過了
FrameworkServlet中的initServletBean()源碼

/**
     * Overridden method of {@link HttpServletBean}, invoked after any bean properties
     * have been set. Creates this servlet's WebApplicationContext.
     */
    @Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                    elapsedTime + " ms");
        }
    }

可以看到initServletBean()中具體的處理都是通過調(diào)用initWebApplicationContext()方法實(shí)現(xiàn)的茄袖,具體源碼如下:

/**
     * Initialize and publish the WebApplicationContext for this servlet.
     * <p>Delegates to {@link #createWebApplicationContext} for actual creation
     * of the context. Can be overridden in subclasses.
     * @return the WebApplicationContext instance
     * @see #FrameworkServlet(WebApplicationContext)
     * @see #setContextClass
     * @see #setContextConfigLocation
     */
    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

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

        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            onRefresh(wac);
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

initWebApplicationContext()正如方法名的直觀含義所表示的,它的作用就是初始化webApplicationContext嘁锯,主要步驟有:
1宪祥、通過servletcontext獲取ContextLoaderListener中初始化的根WebApplicationContext
2、調(diào)用onRefresh(rootContext)來對webApplicationContext進(jìn)行初始化(主要是加載mvc相關(guān)的BeanDefinition到webApplicationContext中)
問題: ContextLoaderListener和FrameworkServlet類中都有initWebApplicationContext()方法家乘,二者有什么區(qū)別蝗羊? 解答: a) ContextLoaderListener是監(jiān)聽Servlet的啟動(dòng)/結(jié)束,在啟動(dòng)時(shí)調(diào)用initWebApplicationContext()初始化web的根應(yīng)用上下文仁锯; b) FrameworkServlet調(diào)用initWebApplicationContext()時(shí)耀找,會(huì)先取ContextLoaderListener初始化的web根應(yīng)用上下文,對其進(jìn)行mvc相關(guān)的初始化:FrameworkServlet的initWebApplicationContext()方法通過調(diào)用DispatcherServlet的onRefresh()和initStrategies()方法實(shí)現(xiàn)對mvc相關(guān)的BeanDefinition加載(具體需要接下來看initStrategies()方法源碼)

由于DispatcherSerlvet的onRefresh直接調(diào)用了initStrategies()方法业崖,我們看initStrategies()的源碼:

/**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

我們逐個(gè)解讀這些初始化方法,他們都是從webApplicationContext中通過getBean()方法獲取BeanDefinition的實(shí)例:
1.以initMultipartResolver為例:

    /**
     * Initialize the MultipartResolver used by this class.
     * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
     * no multipart handling is provided.
     */
    private void initMultipartResolver(ApplicationContext context) {
        try {
            this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
            }
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Default is no multipart resolver.
            this.multipartResolver = null;
            if (logger.isDebugEnabled()) {
                logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
                        "': no multipart request handling provided");
            }
        }
    }

initMultipartResolver從webApplicationContext獲取名為“multipartResolver”的Bean定義野芒,如果spring的配置中沒有配置的話,webApplicationContext也獲取不到該Bean定義双炕,則multipartResolver不生效

這些初始化方法分別對應(yīng)spring mvc配置中的這些Bean:
multipartResolver
localeResolver
themeResolver
handlerMapping:指定handlerMapping的處理類
handlerAdapter
handlerExceptionResolver
viewNameTranslator
viewResolver:指定視圖解析類
flashMapManager
例如:viewResolver配置


我們重點(diǎn)看下initHandlerMappings()的源碼狞悲,他是從mvc中重要的url mapping的基礎(chǔ):

    /**
     * Initialize the HandlerMappings used by this class.
     * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
     * we default to BeanNameUrlHandlerMapping.
     */
    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<>(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");
            }
        }
    }

這段源碼的主要作用是指定用于handlerMapping的具體處理類,并將其賦值給DispatcherServlet的handlerMappings字段妇斤,可以單步調(diào)試看下HandlerMapping的具體值:


可以看到handleMappings默認(rèn)值有3個(gè): 1.RequestMappingHandlerMapping 2.BeanNameUrlHandlerMapping 3.SimpleUrlHandlerMapping

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摇锋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子站超,更是在濱河造成了極大的恐慌荸恕,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件死相,死亡現(xiàn)場離奇詭異戚炫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)媳纬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門双肤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來施掏,“玉大人,你說我怎么就攤上這事茅糜∑甙牛” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵蔑赘,是天一觀的道長。 經(jīng)常有香客問我缩赛,道長,這世上最難降的妖魔是什么酥馍? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮旨袒,結(jié)果婚禮上汁针,老公的妹妹穿的比我還像新娘。我一直安慰自己施无,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布猾骡。 她就那樣靜靜地躺著,像睡著了一般敷搪。 火紅的嫁衣襯著肌膚如雪卓练。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天襟企,我揣著相機(jī)與錄音,去河邊找鬼顽悼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蔚龙,可吹牛的內(nèi)容都是我干的映胁。 我是一名探鬼主播木羹,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼抛人!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妖枚,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤苍在,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后寂恬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酷鸦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年朴译,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了属铁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眠寿。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盯拱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狡逢,到底是詐尸還是另有隱情,我是刑警寧澤奢浑,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布腋腮,位于F島的核電站,受9級特大地震影響即寡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜聪富,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一莺丑、第九天 我趴在偏房一處隱蔽的房頂上張望墩蔓。 院中可真熱鬧萧豆,春花似錦蟹漓、人聲如沸炕横。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗽交。三九已至,卻和暖如春夫壁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盒让。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姨蝴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓左医,卻偏偏與公主長得像,于是被迫代替她去往敵國和親同木。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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

  • 閱讀前提:1秕硝、理解IOC的一些概念洲尊,以及在Spring中的實(shí)現(xiàn)(上下文,BeanFactory颊郎,BeanDefin...
    測試你個(gè)頭閱讀 1,038評論 0 4
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)榛做,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 在使用SpringMVC時(shí)會(huì)在Web.xml配置如下代碼: 先看看DispatcherServlet類的繼承關(guān)系 ...
    zlb閱讀 1,682評論 0 2
  • 一检眯、環(huán)境搭建 創(chuàng)建一個(gè)web項(xiàng)目。 如果是maven項(xiàng)目刽严,則直接在pom中加入springMvc依賴 如果不是就從...
    zxcvbnmzsedr閱讀 1,511評論 0 1
  • 今天朋友小何給我打來電話避凝,說結(jié)婚之后一直比較忙碌舞萄,沒有來得急感謝我在他辦結(jié)婚酒時(shí)給他的幫忙管削,我說好朋友之間也沒不需...
    憤怒的老鳥閱讀 244評論 0 2