Spring mvc 上下文初始化過程

在軟件開發的中,若是某些特性的使用比較廣泛,那麼這些特性每每能夠做爲平臺特性來實現,經過對這些平臺特性進行有效的封裝,使其向其餘應用開放。正是如此,Spring因爲其IOC、AOP、事務處理、持久化驅動等特色,使得其起到了一個應用平臺的做用。Spring MVC是Spring的一個重要的模塊,其web應用的實現,是由Spring的來支撐的,Spring MVC的是實現也是依託再Spring平臺提供的基礎特性的。本文主要是介紹Spring mvc容器初始化的過程,從中能夠看出Spring MVC的對於Spring的依賴。html

1、從Web應用角度看Spring MVC 在Servlet模型中,請求-響應的實現依賴於兩大元素的共同配合:java

  1. 配置Servlet及其映射關係(在web.xml中)
  2. 在Servlet實現類中完成響應邏輯

項目規模擴大以後,請求-響應的映射關係所有定義在web.xml中,將形成web.xml的不斷膨脹而變得難以維護。針對這個問題,SpringMVC提出的方案就是:提煉一個核心的Servlet覆蓋對全部Http請求的處理。這一被提煉出來的Servlet,一般被咱們稱之爲:核心分發器。在SpringMVC中,核心分發器就是org.springframework.web.servlet.DispatcherServlet。web

核心分發器要解決的是下面兩個問題:spring

問題1:核心Servlet應該可以創建起一整套完整的對全部Http請求進行規範化處理的流程。編程

問題2:核心Servlet應該可以根據必定的規則對不一樣的Http請求分發到不一樣的Servlet對象上去進行處理。json

針對上面的這個兩個問題,SpringMVC的解決方案是:將整個處理流程規範化,並把每個處理步驟分派到不一樣的組件中進行處理。服務器

處理流程規範化 :將處理流程劃分爲若干個步驟(任務),並使用一條明確的邏輯主線將全部的步驟串聯起來 處理流程組件化 : 將處理流程中的每個步驟(任務)都定義爲接口,併爲每一個接口賦予不一樣的實現模式session

處理流程規範化的首要內容就是考慮一個通用的Servlet響應程序大體應該包含的邏輯步驟:mvc

對Http請求進行初步處理,查找與之對應的Controller處理類(方法) 調用相應的Controller處理類(方法)完成業務邏輯 對Controller處理類(方法)調用時可能發生的異常進行處理 根據Controller處理類(方法)的調用結果,進行Http響應處理 所謂的組件化,實際上也就是使用編程語言將這些邏輯語義表達出來。在Java語言中,最適合表達邏輯處理語義的語法結構是接口,而接口能夠有不一樣的實現,所以上述的四個流程也就被定義爲了四個不一樣接口,它們分別是:app

HandlerMapping HandlerAdapter HandlerExceptionResolver ViewResolver 2、從Spring角度看Spring MVC 從上面能夠看出,組件是核心分發器(DispatchServlet)的核心所在,它們是http請求處理的邏輯載體,DispatcherServlet是邏輯處理的調度中心,組件則是被調度的操做對象。而Spring容器在這裏所起到的做用,是協助DispatcherServlet更好地對組件進行管理。

咱們知道,SpringMVC的組件是一個個的接口定義,當咱們在SpringMVC的核心配置文件中定義一個組件時,使用的倒是組件的實現類,用具體的實現類來指定組件的行爲模式,不一樣的實現類表明了不一樣的行爲模式,它們在Spring中是能夠共存的。Spring容器對這些實現類進行管理,具體如何使用,由應用程序自己來決定。

上圖是Spring官方reference中的一幅圖,DispatchServlet對外接收http的請求,而請求的處理的是依靠組件來完成的,組件的接口實現的是依靠Spring IOC容器(WebApplicationContext)來管理。從這個圖中咱們能夠看出,Spring MVC實現web應用是依賴與Spring提供的基礎特性(IOC等)。圖中的兩個WebApplicationContext的區別,留到下面再講。

3、Spring MVC 入口配置文件web.xml Spring mvc 有哪些配置文件:

入口配置文件:web.xml;由web或應用服務器爲每一個web項目加載的配置文件。 應用上下文:包括web框架特有配置文件:SpringMVC的${dispatcherServletName}-servlet.xml配置文件。和Spring的配置文件applicationContext.xml,applicationContext-*.xml。 遵循servlet規範,Spring MVC的web應用的入口配置文件也是web.xml。web容器的初始化首先是加載web.xml文件,Jetty在啓動時首先加載此配置文件,而且對其中定義的listener和servlet等進行相應的加載和初始化。Jetty並不清楚(也不關心)其餘配置文件的存在,所以,加載其餘配置文件應該是你(框架)的事情。那麼怎麼加載呢?前面說過Jetty在啓動時會自動加載和初始化listener和servlet,那麼咱們能夠自定義一個listener(ContextLoaderListener)或servlet(DispatcherServlet),Jetty會根據web.xml加載和初始化他們,而他們則負責加載相應的配置文件。

在web.xml配置文件中,有兩個主要的配置:ContextLoaderListener和DispatcherServlet。一樣的關於spring配置文件的相關配置也有兩部分:context-param和DispatcherServlet中的init-param。那麼,這兩部分的分別表示是Spring 容器的初始化和web容器的初始化。DispatcherServlet和ContextLoaderListener提供了在Web容器中對spring的接口。ServletContext爲Spring的IOC容器提供了一個宿主環境,在宿主環境中,Spring MVC創建起了一個IOC容器體系。

下面看一下Spring mvc 中web.xml文件的相關配置內容:

push-center-server test webAppRootKey test contextConfigLocation classpath:applicationContext.xml

<context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>classpath:log4j2.xml</param-value>
</context-param>


<!-- 項目使用的配置文件位置.項目啓動自動讀取 -->
<context-param>
    <param-name>propertiesConfigLocation</param-name>
    <param-value>/WEB-INF/conf/config.properties</param-value>
</context-param>


<!--jmonitor-->
<context-param>
    <param-name>jmonitor-configfile</param-name>
    <param-value>jmonitor.properties</param-value>
</context-param>
<listener>
    <listener-class>com.meituan.jmonitor.servlet.ContextListener</listener-class>
</listener>


<listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>


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




<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>




<!--jmonitor http collector-->
<filter>
    <filter-name>JMonitorHttpMonitorFilter</filter-name>
    <filter-class>com.meituan.jmonitor.collector.http.HttpMonitorFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>JMonitorHttpMonitorFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>


<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>com.sankuai.meituan.web.MtDispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:webmvc-config.xml</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
</servlet>


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

 

4、Spring IOC容器(根上下文)的初始化 Spring Framework自己沒有Web功能,Spring MVC使用WebApplicationContext接口擴展ApplicationContext,使得擁有web功能,WebApplicationContext接口默認的實現是XmlWebApplicationContext。那麼,Spring MVC是如何在web環境中建立IoC容器呢?

先看一下WebApplicationContext的源碼,

WebApplicationContext

public interface WebApplicationContext extends ApplicationContext { //用於在ServletContext中存取根上下文 String ROOTWEBAPPLICATIONCONTEXTATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

String SCOPE_REQUEST = "request";

String SCOPE_SESSION = "session";

String SCOPE_GLOBAL_SESSION = "globalSession";

String SCOPE_APPLICATION = "application";

String SERVLET_CONTEXT_BEAN_NAME = "servletContext";

String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";

String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

//取得當前web容器的ServletContext
ServletContext getServletContext();

}

在Spring MVC中,Spring Context是以父子的繼承結構存在的。Web環境(ServletContext)中存在一個根上下文,這個Context是整個應用的根上下文,是其餘context的雙親Context。同時Spring MVC也對應的持有一個獨立的Context,它是根上下文的子上下文。

由ContextLoaderListener首先啓動的上下文爲根上下文,該上下文是與ServletContext相伴而生的,在根上下文的基礎上,Spring MVC對應持有的一個用來管理控制器須要的對象的子上下文。下圖是ContextLoaderListener繼承關係,它實現了ServletContextListener接口,這個接口提供了與Servlet生命週期結合的回調,好比contextInitialized和contextDestroyed方法。創建WebApplicationContext的過程是在contextInitialized的接口實現中完成的,具體的載入IOC容器的過程是由ContextLoader來完成的。

ContextLoaderListener

`public class ContextLoaderListener extends ContextLoader implements ServletContextListener { private ContextLoader contextLoader;

public ContextLoaderListener() {
}

public ContextLoaderListener(WebApplicationContext context) {
    super(context);
}

public void contextInitialized(ServletContextEvent event) {
    this.contextLoader = createContextLoader();
    if (this.contextLoader == null) {
        this.contextLoader = this;
    }
    this.contextLoader.initWebApplicationContext(event.getServletContext());
}

[@Deprecated](https://my.oschina.net/jianhuaw)
protected ContextLoader createContextLoader() {
    return null;
}

[@Deprecated](https://my.oschina.net/jianhuaw)
public ContextLoader getContextLoader() {
    return this.contextLoader;
}


public void contextDestroyed(ServletContextEvent event) {
    if (this.contextLoader != null) {
        this.contextLoader.closeWebApplicationContext(event.getServletContext());
    }
    ContextCleanupListener.cleanupAttributes(event.getServletContext());
}

}`

從ContextLoaderListener源碼能夠看出,實現的是ServletContextListener接口,這個接口裏的函數會結合Web容器的生命週期被調用。由於ServletContextListener是ServletContext的監聽者,當servletContext啓動或者中止的時候,會觸發響應的事件,監聽器ServletContextListener會接收到事件,會作出相應的響應,好比這裏的contextInitialized方法和contextDestroyed方法。Spring IOC容器(根上下文)的生成與銷燬就是經過這個兩個方法的,因此根上下文是與ServletContext相伴而生的。

因此當Web應用啓動時,contextInitialized方法會執行載入根上下文(IOC容器),具體過程是首先從Servlet事件中獲得ServletContext,而後以ServletContext爲宿主環境,載入根上下文(IOC容器),具體的載入過程是由ContextLoader處理的。

下圖所示爲根上下文的加載過程,下面將結合源碼來看一下這個過程是如何實現的。

根上下文的載入過程:

ROOT Context是在ContextLoaderListener中配置的,ContextLoaderListener讀取context-param中的contextConfigLocation指定的配置文件,建立ROOT Context。下面看一下ContextLoaderListener中建立context的源碼:

 

ContextLoader加載根上下文的源碼

 

//這裏開始對WebApplicationContext進行初始化
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  
//在整個web應用中,只能有一個根上下文,判斷ServletContext中是否已經有根上下文
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException(
                    "Cannot initialize context because there is already a root application context present - " +
                    "check whether you have multiple ContextLoader* definitions in your web.xml!");
        }
        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();
        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            if (this.context == null) {
// 執行了建立WebApplicationContext的操做
                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中
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }
            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }
  
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 根據web.xml中的配置,決定用那種WebApplicationContext,默認用XmlWebApplicationContext
        Class<?> contextClass = determineContextClass(sc);
//判斷contextClass是否繼承ConfigurableWebApplicationContext或者是其接口實現
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
//直接實例化須要產生的IOC容器
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
  
//設置IOC容器各個參數,而後經過refresh啓動容器的初始化
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
//設置application context ID
        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
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                wac.setId(idParam);
            }
            else {
                // Generate default id...
                if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
                    // Servlet <= 2.4: resort to name specified in web.xml, if any.
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                            ObjectUtils.getDisplayString(sc.getServletContextName()));
                }
                else {
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                            ObjectUtils.getDisplayString(sc.getContextPath()));
                }
            }
        }
//設置ServletContext
        wac.setServletContext(sc);
//設置配置文件的位置參數
        String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (initParameter != null) {
            wac.setConfigLocation(initParameter);
        }
//在refresh以前,根據配置的config locations從新定製(Customize)ConfigurableWebApplicationContext
        customizeContext(sc, wac);
//啓動容器的初始化
        wac.refresh();
    }

從上面的源碼中能夠看出來,IOC容器在web容器中的啓動過程,與在應用中啓動IOC容器的方式類似,不一樣的是這裏須要考慮web容器的環境的特色,好比各類參數的設置,IOC容器與web容器ServletContext的結合等。在初始化上下文之後,該上下文被存儲再ServletContext中,這樣就創建了一個全局的關於整個應用的上下文,即所謂的根上下文。同時在啓動Spring MVC的時候,DispatchServlet在進行本身持有的上下文的初始化時,是將此根上下文設置爲本身的雙親上下文的。

http://blog.csdn.net/and1kaney/article/details/51214193 這篇文章能夠看到根容器初始化過程的整個的call hierarchy。

5、Spring MVC容器(子上下文)的初始化 以上是web容器中根上下文的加載與初始化,在完成對ContextLoaderListener的初始化之後,web容器開始初始化DispatchServlet,DispatchServlet會創建本身的上下文來管理Spring MVC的bean對象。在創建這個本身持有的上下文的時候,會從ServletContext中獲得根上下文做爲DispatchServlet持有的上下文的雙親上下文,再對本身持有的上下文進行初始化,最後把本身持有的這個上下文也保存到ServletContext中。

咱們先看下DispatchServlet的繼承關係,以下圖。DispatchServlet經過繼承FrameworkServlet和HttpServletBean而繼承了HttpServlet。HttpServletBean是Spring對於Servlet最低層次的抽象。在這一層抽象中,Spring會將這個Servlet視做是一個Spring的bean,並將web入口配置文件web.xml中DispatchServlet定義的init-param參數中的值做爲bean的屬性注入進來。

DispatcherServlet也是一個Servlet,根據Servlet規範的定義,Servlet中的兩大核心方法init方法和service方法:

  1. init方法

在整個系統啓動時運行,且只運行一次。所以,在init方法中咱們每每會對整個應用程序進行初始化操做。這些初始化操做可能包括對容器(WebApplicationContext)的初始化、組件和外部資源的初始化等等。

  1. service方法

在整個系統運行的過程當中處於偵聽模式,偵聽並處理全部的Web請求。所以,在service及其相關方法中,咱們看到的則是對Http請求的處理流程。

這篇文章主要是介紹Spring mvc 容器的初始化,因此主要是介紹iDisipatchServlet的init方法。

HttpServletBean

@Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }
        // Set bean properties from init parameters.
        try {
//讀取web.xml中DispatchServlet定義中的<init-param>,對Bean屬性進行配置
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//生成一個BeanWrapper,將當前的這個Servlet類轉化爲一個BeanWrapper,從而可以以Spring的方式來對init-param的值進行注入
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
//將init-param中的值注入
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }
        // 調用子類的initServletBean進行具體的初始化
        initServletBean();
        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

 FrameworkServlet則是在HttpServletBean的基礎之上的進一步抽象。經過FrameworkServlet真正初始化了一個Spring的容器(WebApplicationContext),並引入到Servlet對象之中:

FrameworkServlet

/**
     * 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 {
//對Spring的容器(webApplicationContext)進行初始化
            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");
        }
    }
  
protected WebApplicationContext initWebApplicationContext() {
//這裏調用WebApplicationContextUtils靜態類來從ServletContext中獲得根上下文,使用這個根上下文做爲當前MVC上下文的雙親上下文。
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
        if (this.webApplicationContext != null) {
            // 若是一個context的實例被注入了,直接用
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // 若是此上下文尚未初始化,就設置上下文的參數,如雙親上下文、application context id等
                    if (cwac.getParent() == null) {
                        // 若是被注入的context實例沒有雙親上下,則將根上下文設置爲其雙親上下文
                        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
//沒有注入的context實例,這裏從ServletContext中查找是否有context實例已經註冊到了servlet context了
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
//在ServletContext沒有context實例,因此須要建立一個WebApplicationContext,以根上下文爲雙親下文建立
            wac = createWebApplicationContext(rootContext);
        }
//刷新上下文(執行組件的初始化),這個方法由子類DispatchServlet的方法實現
        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);
        }
//把當前的上下文存到ServletContext中去,使用的屬性名是和當前的Servlet名相關的
        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;
    }
  
//建立上下文
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
// 根據web.xml中的配置,決定用那種WebApplicationContext,默認用XmlWebApplicationContext
        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;
    }

在這個調用關係中,能夠看到MVC的初始化是再DispatchServlet的initStrategies方法中完成的,包括對各類MVC框架的實現元素,好比支持國際化的LocaleResolver、支持request映射的HandlerMappings、以及視圖生成的ViewResolver等的初始化。對於具體實現元素的初始化就不一一列出源碼了,這裏以HandlerMappings爲例來講明MVC框架元素的初始化過程。

DispatchServlet

 

@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.
     */
////初始化默認的Spring Web MVC框架使用的策略(如HandlerMapping)
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
  
private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
//這裏導入全部的HandlerMapping Bean,這些Bean能夠是在當前的DispatchServlet的IOC容器,也能夠是其雙親上下文中的,這裏detectAllHandlerMappings默認是爲true的,從全部的IOC容器中取
        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.
                OrderComparator.sort(this.handlerMappings);
            }
        }
        else {
//從當前IOC容器中經過getBean獲取handlerMapping
            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.
            }
        }
//若是沒有找到handlerMappings,設置默認的handlerMapping,默認值設置在DispatcherServlet.properties中
        // 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");
            }
        }
    }

6、Spring MVC 上下初始化流程圖

參考:

http://blog.csdn.net/c289054531/article/details/9196149

http://blog.csdn.net/prince2270/article/details/5889117

http://blog.arganzheng.me/posts/config-file-in-web-application.html

相關文章
相關標籤/搜索