詳解Spring MVC:上

?本章將分析Spring MVC自身的創(chuàng)建過程懂鸵。首先分析Spring MVC的整體結(jié)構(gòu)契沫,然后具體分析每一層的創(chuàng)建過程。

9.1 整體結(jié)構(gòu)介紹

?Spring MVC中核心Servlet的繼承結(jié)構(gòu)如下圖所示。

Spring MVC核心Servlet結(jié)構(gòu)圖.png

?可以看到在Servlet的繼承結(jié)構(gòu)中有一共有5個(gè)類氮凝,GenericServlet和HttpServlet在java中谜洽,前面已經(jīng)講過萝映,剩下的三個(gè)類HttpServletBean、FrameworkServlet和DispatcherServlet是Spring MVC中的阐虚,本章主要講解這三個(gè)類的創(chuàng)建過程序臂。
?這三個(gè)類直接實(shí)現(xiàn)三個(gè)接口:EnvironmentCapable、EnvironmentAware和ApplicationContextAware实束。XXXWare在Spring里表示對(duì)XXX可以感知奥秆,通俗點(diǎn)解釋就是:如果在某個(gè)類里面想要使用spring的一些東西,就可以通過實(shí)現(xiàn)XXXWare接口告訴Spring咸灿,Spring看到后就會(huì)給你送過來(lái)吭练,而接收的方式是通過實(shí)現(xiàn)接口唯一的方法set-XXX。比如析显,有一個(gè)類想要適應(yīng)當(dāng)前的ApplicationContext鲫咽,那么我們只需要讓它實(shí)現(xiàn)ApplicationContextAware接口,然后實(shí)現(xiàn)接口中唯一的方法void setApplicationContext (ApplicationContext applicationContext)就可以了谷异,Spring會(huì)自動(dòng)調(diào)用這個(gè)方法將applicationContext傳給我們分尸,我們只需要接受就可以了!EnvironmentCapable歹嘹,顧名思義箩绍,當(dāng)然就是具有Environment的能力,也就是提供Environment尺上,所以EnvironmentCapable唯一的方法是Environment getEnvironment()材蛛,用于實(shí)現(xiàn)EnvironmentCapable接口的類,就是告訴Spring它可以提供Environment怎抛,當(dāng)Spring需要Environment的時(shí)候就會(huì)調(diào)用其getEnvironment方法跟它要卑吭。
?了解了Aware和Capable的意思,下面再來(lái)看一個(gè)ApplicationContext和Environment马绝。前者詳細(xì)大家都很熟悉了豆赏,后者是環(huán)境的意思,具體功能與之前講過的ServletContext有點(diǎn)類似。實(shí)際上在HttpServletBean中Environment使用的是Standard-Servlet-Environment(在createEnvironment方法中創(chuàng)建)掷邦,這里確實(shí)封裝了ServletContext白胀,同時(shí)還封裝了ServletConfig、JndiProperty抚岗、系統(tǒng)環(huán)境變量和系統(tǒng)屬性或杠,這些都封裝到了其propertySources屬性下。為了讓大家理解得更深刻宣蔚,在前面項(xiàng)目的GoController中獲取Environment,然后通過調(diào)試看一下向抢。首先讓GoController實(shí)現(xiàn)EnvironmentAware接口,這樣Spring就會(huì)將Environment傳給我們件已,然后在處理請(qǐng)求的方法中加入斷點(diǎn)笋额,這樣就可以查看Spring傳進(jìn)來(lái)的內(nèi)容了元暴。修改后代碼如下:

/**
 * EnvironmentAware接口測(cè)試
 *
 * @version
 * @author kyle 2018年8月26日上午10:34:04
 * @since 1.8
 */
@Controller
public class GoController implements EnvironmentAware {
    private final Logger logger = LoggerFactory.getLogger(GoController.class);

    private Environment environment = null;

    @RequestMapping(value = { "/" }, method = { RequestMethod.HEAD })
    public String head() {
        return "go.jsp";
    }

    @RequestMapping(value = { "/index", "/" }, method = { RequestMethod.GET })
    public String index(Model model) throws Exception {
        logger.info("==========processed by index=========");
        // 這里設(shè)置斷點(diǎn)
        model.addAttribute("msg", "Go Go Go!");
        return "go.jsp";
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

}

9.2 HttpServletBean

?通過前面對(duì)Sevlet的分析篷扩,我們知道Servlet創(chuàng)建時(shí)可以直接調(diào)用無(wú)參數(shù)的init方法。HttpServletBean的init方法如下:

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    ...
    @Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        try {
            //將Servlet中配置的參數(shù)封裝到pvs變量中茉盏,requiredProperties為必需參數(shù)鉴未,如果沒有配置將報(bào)異常
            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()));
            //模版方法,可以在子類調(diào)用鸠姨,做一些初始化工作铜秆。bw代表DispatcherServlet
            initBeanWrapper(bw);
            //將配置對(duì)初始化值(如contextConfigLocation)設(shè)置DispatcherServlet
            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.
        //模版方法,子類初始化對(duì)入口方法
        initServletBean();

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

?可以看到讶迁,在HttpServletBean的init中连茧,首先將Servlet中配置對(duì)參數(shù)使用BeanWrapper設(shè)置到DispatcherServlet的相關(guān)屬性,然后調(diào)用模版方法initServletBean巍糯,子類就通過這個(gè)方法初始化啸驯。

BeanWrapper是什么,怎么用

?BeanWrapper是Spring提供的一個(gè)用來(lái)操作JavaBean屬性到工具祟峦,使用它可以直接修改一個(gè)對(duì)象的屬性罚斗,示例如下:

public class User {
    String userName;
    public String getUserName(){
        return userName;
    }
    public void setUserName(String userName){
        this.userName = userName;
    }
}

/**
 * BeanWrapper測(cè)試
 *
 * @version
 * @author kyle 2018年8月26日上午11:45:42
 * @since 1.8
 */
public class BeanWrapperTest {
    public static void main(String[] args) {
        User user = new User();
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(user);
        bw.setPropertyValue("userName", "張三");
        System.out.println(user.getUserName());// 輸出張三
        PropertyValue value = new PropertyValue("userName", "李四");
        bw.setPropertyValue(value);
        System.out.println(user.getUserName());// 輸出李四
    }
}

?這個(gè)例子首先新建一個(gè)User對(duì)象,其次使用PropertyAccessFactory封裝成BeanWrapper對(duì)象宅楞,這樣就可以使用BeanWrapper對(duì)象對(duì)其屬性u(píng)serName進(jìn)行操作针姿。BeanWrapper的使用就是這么簡(jiǎn)單,理解了這個(gè)例子厌衙,就可以理解HttpServletBean中設(shè)置屬性的方法了距淫。以下為測(cè)試補(bǔ)充

BeanWrapper測(cè)試.png

9.3 FrameworkServlet

?從HttpServlet中可知,F(xiàn)rameworkServlet的初始化入口方法應(yīng)該是initServletBean婶希,其代碼如下:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
    @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");
        }
    }
    ...
}

?可以看到這里的核心代碼只有兩句:一句用于初始化WebApplicationContext溉愁,另一句用于初始化FrameworkServlet,而且initFrameworkServlet方法是模版方法,子類可以覆蓋然后在里面做一些初始化的工作拐揭,但子類并沒有使用它撤蟆。這兩句代碼如下:

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

?可見FrameworkServlet在構(gòu)建的過程中到主要作用就是初始化了WebApplication。下面來(lái)看一下initWebApplicationContext方法堂污。

protected WebApplicationContext initWebApplicationContext() {
    //獲取rootContext
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    //如果已經(jīng)通過構(gòu)造方法設(shè)置了WebApplicationContext
    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
        //當(dāng)webApplicationContext已經(jīng)存在ServletContext中時(shí)家肯,通過配置在servlet中的contextAttribute參數(shù)獲取
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        //如果webApplicationContext還沒有創(chuàng)建,則創(chuàng)建一個(gè)
        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.
        //當(dāng)ContextRefresh事件沒有觸發(fā)時(shí)調(diào)用此方法盟猖,模版方法讨衣,可以在子類重寫
        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方法做了三件事:

  • 獲取spring的根容器rootContext。
  • 設(shè)置webApplication并根據(jù)情況調(diào)用onRefresh方法式镐。
  • 將webApplicationContext設(shè)置到ServletContext中反镇。

獲取spring的根容器rootContext

?獲取根容器的原理是,默認(rèn)情況下spring會(huì)將自己的容器設(shè)置成ServletContext的屬性默認(rèn)根容器的key為org.springframework.web.context.WebApplicationContext.ROOT娘汞,定義在org.springframework.web.context.WebApplicationContext中歹茶。

public interface WebApplicationContext extends ApplicationContext {
    ...
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    ...
}

?所以獲取根容器只需要調(diào)用ServletContext的getAttribute就可以了。

    ServletContext#getAttribute("org.springframework.web.context.WebApplicationContext.ROOT")

設(shè)置WebApplicationContext并根據(jù)情況調(diào)用onRefresh方法

?設(shè)置webApplicationContext一共有三種方法你弦。
?第一種方法是在構(gòu)造方法中已經(jīng)傳遞webApplicationContext參數(shù)惊豺,這時(shí)只需要對(duì)其進(jìn)行一些設(shè)置即可。這種方法主要用于Servlet3.0以后的環(huán)境中禽作,Servlet3.0之后可以在程序中使用ServletContext.addServlet方式注冊(cè)Servlet尸昧,這時(shí)就可以在新建FrameworkServlet和其子類的時(shí)候通過構(gòu)成方法傳遞已經(jīng)準(zhǔn)備好的webApplicationContext。
?第二種方法是webApplicationContext已經(jīng)在ServletContext中了旷偿。這時(shí)只需要在配置Servlet的時(shí)候?qū)ervletContext中的webApplicationContext的name配置到contextAttribute屬性就可以了烹俗。比如,在ServletContext中有一個(gè)叫haha的webApplicationContext萍程,可以這么將它配置到Spring MVC中:

<servlet>
    <servlet-name>let'sGo</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextAttribute</param-name>
        <param-value>haha</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

?第三種方法是在前面兩種方法都無(wú)效的情況下自己創(chuàng)建一個(gè)幢妄。正常情況下就是使用的這種方式。創(chuàng)建過程在createWebApplicationContext方法中尘喝,createWebApplicationContext內(nèi)部又調(diào)用了configureAndRefreshWebApplicationContext方法磁浇,代碼如下:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        //獲取創(chuàng)建類型
        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 + "]");
        }
        //檢查創(chuàng)建類型
        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");
        }
        //具體創(chuàng)建
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        wac.setConfigLocation(getContextConfigLocation());
        //將設(shè)置的contextConfigLocation參數(shù)傳給wac,默認(rèn)傳入WEB-INFO/ [ServletName]-Servlet.xml
        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }
    
    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());
        //添加監(jiān)聽ContextRefreshEvent的監(jiān)聽器
        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();
    }
    ...
}

?這里首先調(diào)用getContextClass方法獲取要?jiǎng)?chuàng)建的類型朽褪,它可以通過contextClass屬性設(shè)置到Servlet中置吓,默認(rèn)使用org.springframework.web.context.support.XmlWebApplicationContext然后檢查屬不屬于ConfigurableWebApplicationContext類型,如果不屬于就拋出異常缔赠。接下來(lái)通過BeanUtils.instantiateClass(contextClass)進(jìn)行創(chuàng)建衍锚,創(chuàng)建后將設(shè)置到contextConfigLocation傳入,如果沒有設(shè)置嗤堰,默認(rèn)傳入WEB-INFO/[ServletName]-Servlet.xml戴质,然后進(jìn)行配置。其他內(nèi)容基本上都很容易理解,需要說(shuō)明的是告匠,在configureAndRefreshWebApplicationContext方法中給wac添加了監(jiān)聽器戈抄。

wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

?SourceFilterListener可以根據(jù)輸入的參數(shù)進(jìn)行選擇,所以實(shí)際監(jiān)聽器的是ContextRefreshListener所監(jiān)聽的事件后专。ContextRefreshListener是FrameworkServlet的內(nèi)部類划鸽,監(jiān)聽ContextRefreshedEvent事件,當(dāng)接收到消息時(shí)調(diào)用FrameworkServlet的onApplicationEvent方法戚哎,在onApplicationEvent中會(huì)調(diào)用一次onRefresh方法裸诽,并將refreshEventReceived標(biāo)志設(shè)置為true,表示已經(jīng)refresh過型凳,代碼如下:

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    ...
    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            FrameworkServlet.this.onApplicationEvent(event);
        }
    }
    ...
    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.refreshEventReceived = true;
        onRefresh(event.getApplicationContext());
    }
    ...
}

?再回到initWebApplicationContext方法丈冬,可以看到后面會(huì)根據(jù)refreshEventReceived標(biāo)志來(lái)判斷是否要運(yùn)行onRefresh。

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

?當(dāng)使用第三種方法初始化時(shí)已經(jīng)refresh甘畅,不需要再調(diào)用onRefresh埂蕊。同樣在第一種中也調(diào)用了configureAndRefreshWebApplicationContext方法,也refresh過橄浓,所以只有使用第二種方式初始化webApplicationContext的時(shí)候才會(huì)在這里調(diào)用onRefresh方法粒梦。不過不管用哪種方式調(diào)用亮航,onRefresh最終肯定會(huì)而且只會(huì)調(diào)用一次荸实,而且DispatcherServlet正是通過重寫這個(gè)模版來(lái)實(shí)現(xiàn)初始化的。

將webApplicationContext設(shè)置到ServletContext中

?最后會(huì)根據(jù)publishContext標(biāo)志判斷是否將創(chuàng)建出來(lái)的webApplicationContext設(shè)置到ServletContext的屬性中缴淋,publishContext標(biāo)志可以在配置Servlet時(shí)通過init-param參數(shù)進(jìn)行設(shè)置准给,HttpServletBean初始化時(shí)會(huì)將其設(shè)置到publishContext參數(shù)。之所以將創(chuàng)建出來(lái)的webApplicationContext設(shè)置到ServletContext的屬性中重抖,主要是為了方便獲取露氮,在前面獲取RootApplicationContext的時(shí)候已經(jīng)介紹過。
?前面介紹了配置Servlet時(shí)可以設(shè)置的一些初始化參數(shù)钟沛,總結(jié)如下:

  1. contextAttribute:在ServletContext的屬性中畔规,要用作WebApplicationContext的屬性名稱。
  2. contextClass:創(chuàng)建WebApplicationContext的類型恨统。
  3. contextConfigLocation:Spring MVC配置文件的位置叁扫。
  4. publishContext:是否將webApplicationContext設(shè)置到ServletContext的屬性。

9.4 DispatcherServlet

?onRefresh方法是DispatcherServlet的入口方法畜埋。onRefresh中簡(jiǎn)單地調(diào)用了initStrategies莫绣,在initStrategies中調(diào)用了9個(gè)初始化方法:

public class DispatcherServlet extends FrameworkServlet {
    ...
    /**
     * This implementation calls {@link #initStrategies}.
     */
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    /**
     * 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);
    }
    ...
}

?可能有讀者不理解為什么要這么寫,為什么不將initStrategies的具體實(shí)現(xiàn)直接寫到onRefresh中呢悠鞍?initStrategies方法不是多余的嗎对室?其實(shí)這主要是分層的原因,onRefresh是用來(lái)刷新容器的,initStratgies用來(lái)初始化一些策略組件掩宜。如果把initStrategies里面的代碼直接寫到onRefresh里面蔫骂,對(duì)于程序到運(yùn)行也沒有影響。不過這樣一來(lái)牺汤,如果在onRefresh中想再添加別到功能纠吴,就會(huì)沒有將其單獨(dú)寫一個(gè)方法出來(lái)邏輯清晰,不過這并不是最重要的慧瘤,更重要的是戴已,如果在別的地方也需要調(diào)用initStrategies方法(如需要修改一些策略后進(jìn)行熱部署),但initStrategies沒獨(dú)立出來(lái)锅减,就只能調(diào)用onRefresh糖儡,那樣在onRefresh增加了新功能的時(shí)候就麻煩了。另外單獨(dú)將initStrategies寫出來(lái)還可以被子類覆蓋怔匣,使用新的模式進(jìn)行初始化握联。
?initStrategies的具體內(nèi)容非常簡(jiǎn)單,就是初始化的9個(gè)組件每瞒,下面以LocalResolver為例來(lái)分析具體的初始化方式:

public class DispatcherServlet extends FrameworkServlet {
    ...
    /**
     * Initialize the LocaleResolver used by this class.
     * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
     * we default to AcceptHeaderLocaleResolver.
     */
    private void initLocaleResolver(ApplicationContext context) {
        try {
            //在context中獲取
            this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
            }
        }
        catch (NoSuchBeanDefinitionException ex) {
            // We need to use the default.
            //使用默認(rèn)策略
            this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
                        "': using default [" + this.localeResolver + "]");
            }
        }
    }
    ...
}

?初始化方式為兩步:首先通過context.getBean在容器里面按注冊(cè)時(shí)的名稱或類型(這里指“l(fā)ocaleResolver”名稱或者LocaleResolver.class類型)進(jìn)行查找金闽,所以在Spring MVC的配置文件中只需要配置相應(yīng)類型的組件,容器就可以自動(dòng)找到剿骨。如果找不到就調(diào)用getDefaultStrategy按照類型的組件代芜。需要注意的是,這里的context指的是FrameworkServlet中創(chuàng)建的WebApplicationContext浓利,而不是ServletContext挤庇。下面介紹getDefaultStrategy是怎樣獲取默認(rèn)組件的。

public class DispatcherServlet extends FrameworkServlet {
    ...
    /**
     * Return the default strategy object for the given strategy interface.
     * <p>The default implementation delegates to {@link #getDefaultStrategies},
     * expecting a single object in the list.
     * @param context the current WebApplicationContext
     * @param strategyInterface the strategy interface
     * @return the corresponding strategy object
     * @see #getDefaultStrategies
     */
    protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
        List<T> strategies = getDefaultStrategies(context, strategyInterface);
        if (strategies.size() != 1) {
            throw new BeanInitializationException(
                    "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
        }
        return strategies.get(0);
    }

    /**
     * Create a List of default strategy objects for the given strategy interface.
     * <p>The default implementation uses the "DispatcherServlet.properties" file (in the same
     * package as the DispatcherServlet class) to determine the class names. It instantiates
     * the strategy objects through the context's BeanFactory.
     * @param context the current WebApplicationContext
     * @param strategyInterface the strategy interface
     * @return the List of corresponding strategy objects
     */
    @SuppressWarnings("unchecked")
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        String key = strategyInterface.getName();
        //從defaultStrategies獲取所需策略的類型
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            //如果有多個(gè)默認(rèn)值贷掖,以逗號(hào)分割為數(shù)組
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<T>(classNames.length);
            for (String className : classNames) {
                try {
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    Object strategy = createDefaultStrategy(context, clazz);
                    strategies.add((T) strategy);
                }
                catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                            "Could not find DispatcherServlet's default strategy class [" + className +
                                    "] for interface [" + key + "]", ex);
                }
                catch (LinkageError err) {
                    throw new BeanInitializationException(
                            "Error loading DispatcherServlet's default strategy class [" + className +
                                    "] for interface [" + key + "]: problem with class file or dependent class", err);
                }
            }
            return strategies;
        }
        else {
            return new LinkedList<T>();
        }
    }
    ...
}

?可以看到getDefaultStrategy中調(diào)用了getDefaultStrategies嫡秕,后者返回的是List,這是因?yàn)镠andlerMapping等組件可以有多個(gè)苹威,所以定義了getDefaultStrategies方法昆咽,getDefaultStrategy直接調(diào)用了getDefaultStrategies中實(shí)際執(zhí)行創(chuàng)建的方法是ClassUtils.forName,它需要的參數(shù)是className牙甫,所以最重要的是看classNames掷酗,classNames又來(lái)自value,而value來(lái)自defaultStrategies.getProperty(key)腹暖。所以關(guān)鍵點(diǎn)就在defaultStrategies中汇在,defaultStrategies是一個(gè)靜態(tài)屬性,在static塊中進(jìn)行初始化的脏答。

public class DispatcherServlet extends FrameworkServlet {
    ...
    private static final Properties defaultStrategies;

    static {
        // Load default strategy implementations from properties file.
        // This is currently strictly internal and not meant to be customized
        // by application developers.
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
        }
    }
    ...
}

?我們看到defaultStrategies是DispatcherServlet類所在包下的DEFAULT_STRATEGIES_PREFIX文件里定義的屬性糕殉,DEFAULT_STRATEGIES_PATH的值是DispatcherServlet.properties亩鬼。所以defaultStrategies里面存放的是DispatcherServlet.properties里面定義的鍵值對(duì),代碼如下:

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

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

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

?可以看到阿蝶,這里確定定義了不同組件的類型雳锋,一共定義了8個(gè)組件,處理上傳組件MultipartResolver是沒有默認(rèn)配置的羡洁,這也很容易理解玷过,并不是每個(gè)應(yīng)用都需要上傳功能,即使需要上傳也不一定就要使用MultipartResolver筑煮,所以MultipartResolver不需要默認(rèn)配置辛蚊。另外HandlerMapping、Handler和HandlerExceptionResolver都配置了多個(gè)真仲,其實(shí)ViewResolver也可以有多個(gè)袋马,只是默認(rèn)的配置只有一個(gè)。
?這里需要注意兩個(gè)問題:首先默認(rèn)配置并不是最優(yōu)配置秸应,也不是spring的推薦配置虑凛,只是在沒有配置的時(shí)候可以有個(gè)默認(rèn)值,不至于空著软啼。里面的有些默認(rèn)配置甚至已經(jīng)被標(biāo)注為@Deprecated桑谍,表示已棄用,如DefaultAnnotationHandlerMapping祸挪、AnnotationMethodHandlerAdapter以及AnnotationMethodHandlerExceptionResolver锣披。另外需要注意的一點(diǎn)是,默認(rèn)配置是在相應(yīng)類型沒有配置的時(shí)候才會(huì)使用匕积,如當(dāng)使用<mvc:annoraruon-driven/>后盈罐,并不會(huì)全部使用默認(rèn)配置榜跌。因?yàn)樗渲昧薍andlerMapping闪唆、HandlerAdapter和HandlerExceptionResolver,而且還做了很多別的工作钓葫,更詳細(xì)的內(nèi)容可以查看org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser悄蕾。
?DispatcherServlet的創(chuàng)建過程主要是對(duì)9大組件進(jìn)行初始化,具體每個(gè)組件的作用后面具體講解础浮。

9.5 小結(jié)

?本章主要分析了Spring MVC自身的創(chuàng)建過程帆调,Spring MVC中Servlet一共三個(gè)層次,分別是HttpServletBean豆同、FrameworkServlet和DispatcherServlet番刊。HttpServletBean直接繼承自Java的HttpServlet,其作用是將Servlet中配置的參數(shù)設(shè)置到相應(yīng)的屬性:FrameworkServlet初始化了WebApplicationContext影锈,DispatcherServlet初始化了自身的9個(gè)組件芹务。
?FrameworkServlet初始化WebApplicationContext一共有三種方式蝉绷,過程中使用了Servlet中配置的一些參數(shù)。
?整體結(jié)構(gòu)非常簡(jiǎn)單---分三個(gè)層次做了三件事枣抱,但具體實(shí)現(xiàn)過程還是有點(diǎn)復(fù)雜的熔吗,這其實(shí)也是spring的特點(diǎn):結(jié)構(gòu)簡(jiǎn)單,實(shí)現(xiàn)復(fù)雜佳晶。結(jié)構(gòu)簡(jiǎn)單主要是頂層設(shè)計(jì)好桅狠,實(shí)現(xiàn)復(fù)雜的主要是提供的功能比較多,可配置的地方也非常多轿秧。當(dāng)然中跌,正是因?yàn)閷?shí)現(xiàn)復(fù)雜,才讓Spring MVC使用起來(lái)更加靈活菇篡,這一點(diǎn)在后面會(huì)有深刻多體會(huì)晒他。

寫博原因

?spring mvc是面試官經(jīng)常問的point,不管初級(jí)逸贾、中級(jí)陨仅、高級(jí)程序員折在這上的不少,希望大家面試順利铝侵,在讀完《詳解Spring MVC 上下》之后能震懾住面試官灼伤。《下》我會(huì)在9月初完成咪鲜,感謝觀看.....

《詳解Spring MVC:下》

如果需要給我修改意見的發(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閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炊琉,死亡現(xiàn)場(chǎng)離奇詭異展蒂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)苔咪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門锰悼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人团赏,你說(shuō)我怎么就攤上這事箕般。” “怎么了舔清?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵丝里,是天一觀的道長(zhǎng)可柿。 經(jīng)常有香客問我,道長(zhǎng)丙者,這世上最難降的妖魔是什么复斥? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮械媒,結(jié)果婚禮上目锭,老公的妹妹穿的比我還像新娘。我一直安慰自己纷捞,他們只是感情好痢虹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著主儡,像睡著了一般奖唯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糜值,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天丰捷,我揣著相機(jī)與錄音,去河邊找鬼寂汇。 笑死病往,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的骄瓣。 我是一名探鬼主播停巷,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼榕栏!你這毒婦竟也來(lái)了畔勤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤扒磁,失蹤者是張志新(化名)和其女友劉穎庆揪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渗磅,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嚷硫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年始鱼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片医清。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卖氨,死狀恐怖负懦,靈堂內(nèi)的尸體忽然破棺而出柏腻,到底是詐尸還是另有隱情五嫂,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布躯枢,位于F島的核電站槐臀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏得糜。R本人自食惡果不足惜晰洒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望槽棍。 院中可真熱鬧,春花似錦炼七、人聲如沸布持。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)胧卤。三九已至,卻和暖如春况芒,著一層夾襖步出監(jiān)牢的瞬間叶撒,已是汗流浹背耐版。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工粪牲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留止剖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓舌狗,卻偏偏與公主長(zhǎng)得像扔水,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子主届,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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