Spring MVC之DispatcherServlet初始化詳解

       Spring作爲一個優秀的web框架,其運行是基於Tomcat的。在我們前面的講解中,Spring的驅動都是使用的ClassPathXmlApplicationContext,並且都是直接在main方法中啓動的,但是在Tomcat容器中,我們是無法使用main方法的,因而其驅動方式必然與我們測試時不一樣。Tomcat是一個基於Servlet規範的web容器,而Spring則提供了對Servlet規範的支持,其DispatcherServlet則是Servlet規範的具體實現。因而在web開發過程中,當我們啓動Tomcat容器時其會根據Servlet規範啓動Spring實現的DispatcherServlet,這樣也就驅動了Spring的運行。本文主要從源碼的角度講解Spring在web容器中是如何初始化的。

1. web.xml配置

       在配置web容器時,我們都會配置一個web.xml,而在配置web.xml時,最主要的兩個組件就是ContextLoaderListenerDispatcherServlet的配置。如下是一個典型的web.xml文件的配置:

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

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<servlet>
    <servlet-name>myservlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

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

       這裏ContextLoaderListener的作用是對對Servlet Context的行爲進行監聽,其實現了ServletContextListener接口,這個接口聲明如下:

public interface ServletContextListener extends EventListener {
    // 用於在Servlet Context初始化事前執行
    default public void contextInitialized(ServletContextEvent sce) {}
    
    // 用於在Servlet Context被銷燬之後執行
    default public void contextDestroyed(ServletContextEvent sce) {}
}

       這裏ServletContextListener是Servlet規範中提供的一個接口,該接口中的contextInitialized()會在servlet context初始化之前執行,而contextDestroyed()方法則會在servlet context被銷燬之後執行。Spring提供的ContextLoaderListener對這兩個方法都進行了實現。實際上,在web.xml中指定的contextConfigLocation參數的解析就是在ContextLoaderListener.contextInitialized()方法中解析的,也就是說Spring對於bean的創建實際上是在Servlet Context初始化之前就已經完成了。

       web.xml中配置的DispatcherServlet則是Servlet規範中HttpServlet的一個具體實現,實現了該接口的之後該類就具有處理web請求的能力了。這裏可以看到,DispatcherServlet配置攔截的url是'/',也就是說所有的web請求都會經過DispatcherServlet,而對於具體的url的處理,實際上是在DispatcherServlet中進行分發的。這也就是Spring爲什麼只需要配置一個Servlet的原因。

       關於DispatcherServlet的配置這裏不得不提的是,我們得爲其提供一個myservlet-servlet.xml的配置文件,用於只爲當前servlet提供Spring的一些基本配置。這裏該文件的命名必須按照servlet名稱-servlet.xml這種格式進行,由於我們的servlet的名稱爲myservlet,因而配置文件名必須爲myservlet-servlet.xml。如果使用者需要自定義文件名,可以在當前servlet中使用init-param標籤進行配置,如:

<servlet>
    <servlet-name>myservlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/myservlet-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

       另外,對於配置的myservlet-servlet.xml文件的初始化,是在DispatcherServlet.init()方法中進行的。這裏需要注意的是,myservlet-servlet.xml完完全全是一個獨立的Spring配置文件,我們可以在其中聲明Spring的bean,並且註冊到Spring容器中。

       在web.xml中我們提到了兩個Spring的配置文件,一個是我們常用的applicationContext.xml,另一個專屬於某個Servlet的myservlet-servlet.xml。兩個配置文件的初始化分別是由ServletContextListener.contextInitialized()方法和GenericServlet.init()方法進行的。這兩個方法都是Servlet規範中提供的初始化方法,兩個方法分別會初始化兩個Spring容器,這兩個容器中applicationContext.xml對應的容器會作爲myservlet-servlet.xml初始化的容器的父容器而存在,因而在myservlet-servlet.xml的容器中,我們是可以使用任何在applicationContext.xml中聲明的bean的,但是反過來則不行。在處理具體請求的時候,我們所使用的Spring容器其實一直都是myservlet-servlet.xml聲明而來的。

2. ContextLoaderListener初始化

       對於ContextLoaderListener,其主要是用於初始化我們常用的applicationContext.xml的。如下是其源碼:

public class ContextLoaderListener extends ContextLoader 
        implements ServletContextListener {
    // 初始化Spring容器
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

	// 銷燬Spring容器
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

       可以看到,這裏對於Spring容器的初始化是委託給了initWebApplicationContext()方法進行的,如下是該方法的源碼:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    // 如果當前已經初始化過一個web application context則拋出異常,這樣可以保證一個web容器中
    // 只會有一個web application context
    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 {
        if (this.context == null) {
            // 通過servlet配置創建一個WebApplicationContext對象
            this.context = createWebApplicationContext(servletContext);
        }
        
        // 這裏在createWebApplicationContext()方法中會保證創建的WebApplicationContext本質上是
        // ConfigurableWebApplicationContext類型的,因而這裏進行類型判斷的時候是能夠進入if分支的
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = 
                (ConfigurableWebApplicationContext) this.context;
            // 如果當前WebApplicationContext沒有初始化過就對其進行初始化
            if (!cwac.isActive()) {
                // 如果當前WebApplicationContext沒有父ApplicationContext,則通過
                // loadParentContext()方法加載一個,該方法實際上是一個空方法,這裏提供
                // 出來只是爲了方便用戶進行容器屬性的自定義,因爲父容器的內容會繼承到子容器中
                if (cwac.getParent() == null) {
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                
                // 對WebApplicationContext進行配置,並且調用其refresh()方法初始化
                // 配置文件中配置的Spring的各個組件
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 初始化完成之後將當前WebApplicationContext設置到ServletContext中
        servletContext.setAttribute(WebApplicationContext
            .ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
        
        // 設置當前WebApplicationContext的類加載器
        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;
    }
}

       可以看到,對於WebApplicationContext的初始化,Spring首先會根據配置文件配置創建一個WebApplicationContext對象,然後判斷該對象是否初始化過,如果沒有,則對其進行配置並且初始化。這裏我們首先看看Spring是如何創建WebApplicationContext對象的:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 讀取配置文件中配置的實現了WebApplicationContext接口的類
    Class<?> contextClass = determineContextClass(sc);
    // 判斷讀取到的類是否實現了ConfigurableWebApplicationContext接口,如果沒實現則拋出異常
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" 
            + contextClass.getName() + "] is not of type [" 
            + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    
    // 通過反射實例化WebApplicationContext對象
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

// 這個方法的主要作用在於讀取配置文件中配置的實現了WebApplicationContext接口的類,
// 從而作爲WebApplicationContext容器。這裏讀取配置文件的方式有兩種:①讀取web.xml中配置的
// contextClass屬性,如果存在則將其作爲WebApplicationContext容器;②讀取Spring提供的
// ContextLoader.properties屬性文件中配置的WebApplicationContext容器。
protected Class<?> determineContextClass(ServletContext servletContext) {
    // 讀取用戶在web.xml中使用contextClass屬性自定義的WebApplicationContext容器,
    // 如果不爲空,則直接返回
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, 
                ClassUtils.getDefaultClassLoader());
        } catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                "Failed to load custom context class [" + contextClassName + "]", ex);
        }
    } else {
        // 如果用戶沒有自定義WebApplicationContext,則通過defaultStrategies讀取
        // ContextLoader.properties屬性文件中配置的WebApplicationContext,
        // 這裏讀取到的具體實現類就是XmlWebApplicationContext
        contextClassName = defaultStrategies
            .getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, 
                ContextLoader.class.getClassLoader());
        } catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

       這裏講到,我們可以在web.xml中配置自定義的WebApplicationContext,具體的配置方式就是在web.xml中配置如下屬性:

<context-param>
    <param-name>contextClass</param-name>
    <param-value>mvc.config.MyXmlWebApplicationContext</param-value>
</context-param>

       通過這種方式我們就可以實現自定義的WebApplicationContext。對於Spring提供的默認WebApplicationContext實現,其是通過defaultStrategies這個屬性讀取的,這個屬性的初始化是在ContextLoader(ContextLoaderListener繼承了該類)中使用static代碼塊進行初始化的,讀者可自行查閱。

       在創建了WebApplicationContext對象之後,Spring會對其進行配置和各個組件的初始化,如下是ContextLoader.configureAndRefreshWebApplicationContext()方法的具體實現:

protected void configureAndRefreshWebApplicationContext(
        ConfigurableWebApplicationContext wac, ServletContext sc) {
    // 判斷當前WebApplicationContext是否具有統一的id,如果沒有,首先會從web.xml中讀取,
    // 具體的使用contextId屬性進行制定,該屬性的配置方式與上面的contextClass一致,如果沒有,
    // 則通過默認規則聲明一個
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        } else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                      ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    // 獲取web.xml中配置的contextConfigLocation屬性值,這裏也就是我們前面配置的
    // applicationContext.xml,在後面調用refresh()方法時會根據xml文件中的配置
    // 初始化Spring的各個組件
    wac.setServletContext(sc);
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    // 獲取當前Spring的運行環境,並且初始化其propertySources
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    // 這裏customizeContext()方法是一個空方法,供給用戶自定義實現ContextLoaderListener時
    // 對WebApplicationContext進行自定義
    customizeContext(sc, wac);
    // 這裏refresh()方法用於讀取上面聲明的配置文件,並且初始化Spring的各個組件
    wac.refresh();
}

       關於WebApplicationContext的配置和初始化,這裏主要分爲了四個步驟:①爲當前WebApplicationContext聲明一個id,用於對其進行唯一標識;②讀取web.xml中配置的Spring配置文件的位置;③初始化propertySources;④讀取Spring配置文件中的內容,並且實例化Spring的各個組件。這裏需要說明的是,對於Spring各個組件的初始化,調用的是ConfigurableWebApplicationContext.refresh()方法,這個方法我們前面講解Spring bean註冊解析時已經講解了,讀者可以翻閱Spring Bean註冊解析(一)Spring Bean註冊解析(二)

       在ConfigurableWebApplicationContext.refresh()方法調用完成之後,Spring配置文件中的各項配置就都已經處理完成。如此,ContextLoaderListener的初始化工作也就完成。

3. DispatcherServlet的初始化

       對於DispatcherServlet的初始化,這裏需要注意的是,在web.xml中我們配置了load-on-startup標籤,配置了該標籤就表示當前Servlet的初始化方法會在web容器啓動完成後調用,也就是這裏的DispatcherServlet.init()方法。我們首先看看該方法的源碼:

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

    // 讀取在web.xml中通過init-param標籤設置的屬性,如果沒有配置,這裏pvs就會是empty的
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), 
        this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            // 註冊Resource對象對應的PropertyEditor
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = 
                new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, 
                new ResourceEditor(resourceLoader, getEnvironment()));
            // 初始化BeanWrapper對象,這裏是一個空方法,供給使用者對BeanWrapper進行自定義處理
            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;
        }
    }

    // 初始化當前DispatcherServlet的各項配置
    initServletBean();
    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}

       可以看到,這裏對DispatcherServlet的初始化主要分爲兩個步驟:①判斷當前Servlet中使用使用init-param標籤自定義了屬性,如果定義了,則將其設置到BeanWrapper中;②初始化DispatcherServlet。這裏我們繼續閱讀initServletBean()的源碼:

@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 {
        // 初始化當前servlet配置的Spring配置
        this.webApplicationContext = initWebApplicationContext();
        // 這裏initFrameworkServlet()方法是一個空方法,供給用戶對當前servlet對應的Spring容器
        // 進行自定義的處理
        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()除了進行一些日誌記錄以外,主要工作還是委託給了initWebApplicationContext()方法進行,我們這裏直接閱讀該方法的源碼:

protected WebApplicationContext initWebApplicationContext() {
    // 獲取在ContextLoaderListener中初始化的Spring容器,並且將其作爲當前servlet對應
    // 的容器的父容器,這樣當前servlet容器就可以使用其父容器中的所有內容了
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    
    // 如果當前Servlet對應的WebApplicationContext不爲空,並且其未被初始化,則對其進行初始化
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = 
                (ConfigurableWebApplicationContext) wac;
            // 判斷當前WebApplicationContext是否已初始化過,沒有則進行初始化
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                // 初始化當前WebApplicationContext
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 如果wac爲空,則說明當前servlet對應的WebApplicationContext是空的,
        // 這裏會通過當前servlet配置的contextAttribute屬性查找一個自定義的
        // WebApplicationContext,將其作爲當前servlet的容器
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 如果用戶沒有自定義WebApplicationContext,則創建一個,並且對其進行初始化
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // 這裏的onRefresh()方法並不是初始化Spring配置文件中的bean的,
        // 而是用於初始化Spring處理web請求相關的組件的,如RequestMappingHandlerMapping等
        onRefresh(wac);
    }

    if (this.publishContext) {
        // 將當前WebApplicationContext對象設置到ServletContext中
        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;
}

       這裏默認情況下,DispatcherServlet中是不存在已經初始化過的WebApplicationContext的,因而最終還是會調用createWebApplicationContext()方法進行初始化,在初始化完成之後就會初始化Spring處理web請求的相關組件。我們首先看createWebApplicationContext()方法的實現:

protected WebApplicationContext createWebApplicationContext(@Nullable 
        ApplicationContext parent) {
    // 讀取web.xml中配置的contextClass屬性,將其作爲當前servlet的WebApplicationContext
    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 + "]");
    }
    
    // 保證用戶定義的WebApplicationContext對象是ConfigurableWebApplicationContext類型的
    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");
    }
    
    // 實例化WebApplicationContext對象
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    // 設置當前的運行環境
    wac.setEnvironment(getEnvironment());
    // 將ContextLoaderListener中初始化的WebApplicationContext作爲當前
    // WebApplicationContext的父容器
    wac.setParent(parent);
    // 獲取當前servlet配置的contextConfigLocation
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    // 讀取當前WebApplicationContext配置的Spring相關的bean,並進行初始化
    configureAndRefreshWebApplicationContext(wac);
    return wac;
}

       這裏對當前servlet的WebApplicationContext的初始化過程其實比較簡單,其中最主要需要注意的有兩點:①會將ContextLoaderListener初始化的WebApplicationContext作爲當前WebApplicationContext的父容器;②在獲取當前configLocation的時候,如果沒有設置,則使用"servlet名稱-servlet.xml"的方式讀取。

4. Spring web九大組件初始化

       在第三點最後,我們講到,初始化servlet對應的容器之後,其會調用onRefresh()方法初始化Spring web相關的組件,該方法的具體實現在DispatcherServlet.onRefresh()方法中,這裏我們直接閱讀該方法的源碼:

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

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

       這裏對Spring的九大組件的實例化方式都比較統一,關於這九大組件的具體細節我們後面會依次進行講解。這裏我們主要講解其初始化方式。關於這九大組件,其實例化方式可分爲兩類:

  • 通過制定的bean名稱在Spring容器中讀取對應的bean,如果不存在則使用默認的類來初始化;
  • 通過參數配置控制是在Spring容器中讀取指定實現指定接口的所有bean,還是讀取Spring容器中指定名稱的bean,如果這兩種方式都無法讀取到對應的bean,則讀取Spring配置文件中配置的默認的bean。

       對於第一種實例化方式,我們這裏以LocaleResolver的初始化爲例進行講解,如下是initLocaleResolver()方法的源碼:

private void initLocaleResolver(ApplicationContext context) {
    try {
        // 這裏LOCALE_RESOLVER_BEAN_NAME的值爲localeResolver,也就是說用戶如果
        // 需要自定義的LocaleResolver,那麼在聲明該bean是,其名稱必須爲localeResolver
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, 
            LocaleResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
        }
    } catch (NoSuchBeanDefinitionException ex) {
        // 如果Spring容器中沒有配置自定義的localeResolver,則通過默認策略實例化對應的bean
        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 + "]");
        }
    }
}

       對於第二種方式,我們這裏以HandlerMapping的實例化爲例進行講解,如下是initHandlerMappings()方法的實現原理:

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

    // 檢查是否配置了獲取Spring中配置的所有HandlerMapping類型對象,是則進行讀取,並且按照
    // 指定的排序規則對其進行排序,否則就從Spring中讀取名稱爲handlerMapping的bean,
    // 並將其作爲指定的bean
    if (this.detectAllHandlerMappings) {
        // 從Spring容器中讀取所有的實現了HandlerMapping接口的bean
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
                HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 對獲取到的HandlerMapping進行排序
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        try {
            // 獲取Spring容器中名稱爲handlerMapping的bean
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, 
                HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException ex) {
			// 忽略當前異常
        }
    }

    if (this.handlerMappings == null) {
        // 如果上述方式沒法獲取到對應的HandlerMapping,則使用默認策略獲取對應的HandlerMapping
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" 
                 + getServletName() + "': using default");
        }
    }
}

       上述兩種初始化Spring web組件的方式中都涉及到一個獲取默認的bean的方法,該方法實際上是從Spring提供的配置文件指定對應的bean的Class,在讀取該文件之後會對其進行實例化,然後返回。對於getDefaultStrategies()方法的實現原理,其實比較簡單,我們這裏主要給大家展示Spring提供的組件的配置文件的內容,該配置文件的名稱爲DispatcherServlet.properties,如下是該文件的內容:

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.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\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

       可以看到,這裏的配置文件的key就是對應的接口的全路徑名,這也就是getDefaultStrategies()方法第二個參數傳入的是Class對象的原因,而value就是該接口對應的實現類,可以有多個。

5. 小結

       本文首先講解了web.xml文件的配置方式,並且着重講解了該文件中各個配置的意義,接着依次在源碼的層面對web.xml中配置的各個組件的初始化方式進行了講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章