前段時間在公司做了一個項目,項目用了spring框架實現,WEB容器是Tomct 5,雖然說把項目做完了,但是一直對spring的IoC容器在web容器如何啓動和起作用的並不清楚。所以就抽時間看一下spring的源代碼,藉此瞭解它的原理。
我們知道,對於使用Spring的web應用,無須手動創建Spring容器,而是通過配置文件,聲明式的創建Spring容器。因此在Web應用中創建Spring容器有如下兩種方式:
1. 直接在web.xml文件中配置創建Spring容器。
2. 利用第三方MVC框架的擴展點,創建Spring容器。
其實第一種方式是更加常見。爲了讓Spring容器隨Web應用的啓動而啓動,有如下兩種方式:
1. 利用ServletContextListener實現。
2. 利用load-on-startup Servlet實現。
Spring提供ServletContextListener的一個實現類ContextLoaderListener,該類可以作爲Listener 使用,它會在創建時自動查找WEB-INF下的applicationContext.xml文件,因此,如果只有一個配置文件,並且文件名爲applicationContext.xml,則只需在web.xml文件中增加以下配置片段就可以了
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
如果有多個配置文件需要載入,則考慮使用<context-param...>元素來確定配置文件的文件名。ContextLoaderListener加載時,會查找名爲contentConfigLocation的初始化參數。因此,配置<context-param...>時就指定參數名爲contextConfigLocation。
帶多個配置文件的web.xml文件如下:
<context-param>
<param-name>contextLoaderListener</param-name>
<param-value>
WEB-INF/*.xml, classpath:spring/*.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
多個配置文件之間用“,”隔開。
下面我們來看它的具體實現過程是怎樣的,首先我們從ContextLoaderListener入手,它的代碼如下:
public class ContextLoaderListener implements ServletContextListener
{
private ContextLoader contextLoader;
/**
* 這個方法就是用來初始化web application context的
*/
public void contextInitialized(ServletContextEvent event)
{
this.contextLoader = createContextLoader();
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
/**
* 創建一個contextLoader.
* @return the new ContextLoader
*/
protected ContextLoader createContextLoader()
{
return new ContextLoader();
}
................
}
我們看到初始化web application context的時候,首先通過new ContextLoader()創建一個contextLoader,
new ContextLoader()具體做了什麼事呢?ContextLoader的代碼片段:
static {
try {
// 這裏創建一個ClassPathResource對象,載入ContextLoader.properties,用於創建對應的ApplicationContext容器
// 這個文件跟ContextLoader類在同一個目錄下,文件內容如:
// org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
// 如此說來,spring默認初始化的是XmlWebApplicationContext
ClassPathResource resource = new ClassPathResource(<span style="color:#3366FF;">DEFAULT_STRATEGIES_PATH</span>, ContextLoader.class);
// 得到一個Properties對象,後面根據類名來創建ApplicationContext容器
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
代碼註釋裏面已經說得很清楚了,很容易理解吧?嘿嘿......
再下來我們再看一下initWebApplicationContext方法的實現過程:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
throws IllegalStateException, BeansException {
// 從servletContext中獲取ApplicationContext容器;如果已經存在,則提示初始化容器失敗,檢查web.xml文件中是否定義有多個容器加載器
// ServletContext接口的簡述:public interface ServletContext
// 定義了一系列方法用於與相應的servlet容器通信,比如:獲得文件的MIME類型,分派請求,或者是向日志文件寫日誌等。
// 每一個web-app只能有一個ServletContext,web-app可以是一個放置有web application 文件的文件夾,也可以是一個.war的文件。
// ServletContext對象包含在ServletConfig對象之中,ServletConfig對象在servlet初始化時提供servlet對象。
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!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Determine parent for root web application context, if any.
// 獲取父容器
ApplicationContext parent = loadParentContext(servletContext);
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
// 創建ApplicationContext容器
this.context = createWebApplicationContext(servletContext, parent);
// 把容器放入到servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 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;
}
}
從上面的代碼可以看出,我們創建好的applicationContext容器會放在servletContext中。servletContext是什麼 呢?
在web容器中,通過ServletContext爲Spring的IOC容器提供宿主環境,對應的建立起一個IOC容器的體系。其中,首先需要建立的是根上下文,這個上下文持有的對象可以有業務對象,數據存取對象,資源,事物管理器等各種中間層對象。在這個上下文的基礎上,和web MVC相關還會有一個上下文來保存控制器之類的MVC對象,這樣就構成了一個層次化的上下文結構。
從initWebApplicationContext中可以看到真正創建applicationContext容器是由createWebApplicationContext方法來實現的,它的代碼如下:
protected WebApplicationContext createWebApplicationContext(
ServletContext servletContext, ApplicationContext parent) throws BeansException
{
// 首先決定要創建的applicationContext容器的類
Class contextClass = determineContextClass(servletContext);
// 如果獲取到的類不是ConfigurableWebApplicationContext類型的,則創建容器失敗,所以這裏創建的容器必須是ConfigurableWebApplicationContext類型的
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass))
{
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 實例化spring容器
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setParent(parent);
wac.setServletContext(servletContext);
// 獲取contextConfigLocation初始化參數,該參數記錄的是需要載入的多個配置文件(即定義bean的配置文件)
String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocation != null)
{
wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,
ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
wac.refresh();
return wac;
}
createWebApplicationContext方法實現步驟爲:
1. 首先決定要創建的applicationContext容器的類
2. 實例化applicationContext容器
但它是如何決定要創建的容器類呢?我們看一下determineContextClass方法:
protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException
{
// 從web.xml中獲取需要初始化的容器的類名
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
// 如果獲取到的類名不爲空,則創建該容器的Class對象
if (contextClassName != null)
{
try {
return ClassUtils.forName(contextClassName);
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
// 否則創建默認的容器的Class對象,即:org.springframework.web.context.support.XmlWebApplicationContext
// 在創建ContextLoader時,defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);這句代碼已經準備好默認的容器類
else
{
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try
{
return ClassUtils.forName(contextClassName);
}
catch (ClassNotFoundException ex)
{
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
該方法首先判斷從web.xml文件的初始化參數CONTEXT_CLASS_PARAM(的定義爲public static final String CONTEXT_CLASS_PARAM = "contextClass";)獲取的的類名是否存在,如果存在,則容器的Class;否則返回默認的Class。如何獲取默認的容器Class,注意看創建contextLoader時的代碼註釋就知道了。
由此看來,spring不僅有默認的applicationContext的容器類,還允許我們自定義applicationContext容器類,不過Spring不建義我們自定義applicationContext容器類。
2、利用load-on-startup Servlet實現。
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
好了,這就是spring的IoC容器在web容器如何啓動和起作用的全部過程。細心的朋友可以看出創建applicationContext容器的同時會初始化配置文件中定義的bean類,createWebApplicationContext方法中的wac.refresh();這段代碼就是用來初始化配置文件中定義的bean類的。它具體的實現過程現在還沒完全搞清楚,等搞清楚了再跟大家分享!