在我们使用spring跟tomcat进行结合的时候,我们都会在Resources文件下创建一个webapp/WEB-INF文件夹下面创建一个web.xml文件,在这个文件中我们会添加这么几行配置
org.springframework.web.context.ContextLoaderListener
加上这个配置后Tomcat在启动的时候会去读取web.xml文件中的
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
这个方法会为给定的servlet上下文初始化Spring的Web应用程序上下文,进入到里面调用的initWebApplicationContext方法,这个方法定义在ContextLoaderListener的父类ContextLoader中
public class ContextLoader {
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
...
try {
//如果WebApplicationContext还是null,一般使用web.xml的形式配置的时候,值会为null,这时候使用的是ContextLoaderListener的默认的空构造器
if (this.context == null) {
//创建WebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
//如果WebApplicationContext是ConfigurableWebApplicationContext类型的时候会进行一些处理
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//如果此时的上下文还没有激活,第一次进来的时候是没有激活的即false,当上下文刷新之后,这个值回时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
//如果父上下文还没有设置,就将servletContext设置为父上下文,因为此时很多信息都是里面后面会用到
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参数会被获取
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;
}
//将上下文存储在本地实例变量中,以保证它在ServletContext关闭时可用
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;
}
...
}
}
当我们使用xml的形式配置listener的时候默认WebApplicationContext为null,此时就会自动创建一个WebApplicationContext,所以进入到createWebApplicationContext方法
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//根据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() + "]");
}
//实例化WebApplicationContext
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
接下来进入determineContextClass方法
protected Class> determineContextClass(ServletContext servletContext) {
//如果有实现WebApplicationContext并设置了contextClass的值,则会使用配置的值
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 {
//从defaultStrategies中获取默认的WebApplicationContext的ClassName
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
//根据contextClassName获取Class对象
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
一般情况下我们都不会去设置ServletContext类的contextClass的配置,所以这里会使用默认值,进入到defaultStrategies这个对象,发现这个对象在ContextLoader的静态代码块中被初始化
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
到这里就知道默认实例化的WebApplicationContext是XmlWebApplicationContext。既然上下文实例已经创建了,接下来就是刷新了。这里就要进入到initWebApplicationContext方法中的调用的configureAndRefreshWebApplicationContext方法了,主要看其中的refresh方法。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
...
//前面做的是为上下文刷新之前的做的准备,比如设置ServletContext到spring的上下文中,获取
//刷新上下文
wac.refresh();
...
}
这里的就是开启Spring的容器的刷新了,也是最核心的了。这里调用的是AbstractApplicationContext类的refresh,关于这个前面已经讲解过5.2Spring源码解析——refresh方法,注意在后面调用loadBeanDefinitions方法的时候调用的是XmlWebApplicationContext中的loadBeanDefinitions。