?在我們使用spring跟tomcat進(jìn)行結(jié)合的時候,我們都會在Resources文件下創(chuàng)建一個webapp/WEB-INF文件夾下面創(chuàng)建一個web.xml文件,在這個文件中我們會添加這么幾行配置
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
?加上這個配置后Tomcat在啟動的時候會去讀取web.xml文件中的<listener>和<context-param>兩個結(jié)點(diǎn),接著,會創(chuàng)建一個ServletContext(servlet上下文),這個web項(xiàng)目的所有部分都將共享這個上下文,然后將<context-param>轉(zhuǎn)換為鍵值對鹤盒,并交給servletContext。 還會創(chuàng)建<listener>中的類實(shí)例侦副,創(chuàng)建監(jiān)聽器侦锯。此時就會創(chuàng)建ContextLoaderListener類,在初始化Web應(yīng)用程序中的任何過濾器或servlet之前秦驯,將調(diào)用類中的contextInitialized方法尺碰。
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
?這個方法會為給定的servlet上下文初始化Spring的Web應(yīng)用程序上下文,進(jìn)入到里面調(diào)用的initWebApplicationContext方法,這個方法定義在ContextLoaderListener的父類ContextLoader中
public class ContextLoader {
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
...
try {
//如果WebApplicationContext還是null汇竭,一般使用web.xml的形式配置的時候葱蝗,值會為null,這時候使用的是ContextLoaderListener的默認(rèn)的空構(gòu)造器
if (this.context == null) {
//創(chuàng)建WebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
//如果WebApplicationContext是ConfigurableWebApplicationContext類型的時候會進(jìn)行一些處理
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//如果此時的上下文還沒有激活细燎,第一次進(jìn)來的時候是沒有激活的即false,當(dāng)上下文刷新之后皂甘,這個值回時true
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as setting the parent context, setting the application context id, etc
//如果父上下文還沒有設(shè)置玻驻,就將servletContext設(shè)置為父上下文,因?yàn)榇藭r很多信息都是里面后面會用到
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);
}
//配置并刷新上下文偿枕,會把servletContext中的一些屬性放到spring的上下文中璧瞬,例如如果我們配置了的servlet的initParam參數(shù)會被獲取
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//將spring的上下文保存到servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
//將上下文存儲在本地實(shí)例變量中,以保證它在ServletContext關(guān)閉時可用
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
...
}
}
?當(dāng)我們使用xml的形式配置listener的時候默認(rèn)WebApplicationContext為null渐夸,此時就會自動創(chuàng)建一個WebApplicationContext嗤锉,所以進(jìn)入到createWebApplicationContext方法
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//根據(jù)ServletContext來獲取WebApplicationContext的Class類型
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
//實(shí)例化WebApplicationContext
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
?接下來進(jìn)入determineContextClass方法
protected Class<?> determineContextClass(ServletContext servletContext) {
//如果有實(shí)現(xiàn)WebApplicationContext并設(shè)置了contextClass的值,則會使用配置的值
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
//默認(rèn)情況下是沒有配置的
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 {
//從defaultStrategies中獲取默認(rèn)的WebApplicationContext的ClassName
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
//根據(jù)contextClassName獲取Class對象
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
?一般情況下我們都不會去設(shè)置ServletContext類的contextClass的配置墓塌,所以這里會使用默認(rèn)值瘟忱,進(jìn)入到defaultStrategies這個對象奥额,發(fā)現(xiàn)這個對象在ContextLoader的靜態(tài)代碼塊中被初始化
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
?可以看到defaultStrategies的值是從配置文件ContextLoader.properties中獲取的,所以可以直接打開文件查看
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
?到這里就知道默認(rèn)實(shí)例化的WebApplicationContext是XmlWebApplicationContext访诱。既然上下文實(shí)例已經(jīng)創(chuàng)建了垫挨,接下來就是刷新了。這里就要進(jìn)入到initWebApplicationContext方法中的調(diào)用的configureAndRefreshWebApplicationContext方法了,主要看其中的refresh方法触菜。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
...
//前面做的是為上下文刷新之前的做的準(zhǔn)備九榔,比如設(shè)置ServletContext到spring的上下文中,獲取
//刷新上下文
wac.refresh();
...
}
?這里的就是開啟Spring的容器的刷新了涡相,也是最核心的了哲泊。這里調(diào)用的是AbstractApplicationContext類的refresh,關(guān)于這個前面已經(jīng)講解過5.2Spring源碼解析——refresh方法,注意在后面調(diào)用loadBeanDefinitions方法的時候調(diào)用的是XmlWebApplicationContext中的loadBeanDefinitions催蝗。