ContextLoaderListener这个监听器是启动根IoC容器并把它载入到Web容器的主要功能模块,也是整个Spring Web应用加载IoC的第一个地方。
这个常量用于在ServletContext中存取根上下文
为了方便在Web环境中使用IoC容器,Spring为Web应用提供了上下文的扩展接口WebApplicationContext来满足启动过程的需要,结构图如下:
这个常量用于在ServletContext中存取根上下文
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
通过getServlectContext()方法可以得到当前Web容器的Servlet上下文环境,通过这个方法,相当于提供了一个Web容器级别的全局环境。
在启动过程中,Spring 会使用一个默认的
WebApplicationContext实现作为IoC容器,就是
XmlWebApplicationContext
XmlWebApplicationContext类的结构图
/**
* 这里是设置默认BeanDefinition的地方,如果不特殊指定其他文件,IoC容器会从/WEB-INF/applicationContext.xml文件
* 读取BeanDefinition来初始化IoC容器。
* */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
/** Default prefix for building a config location for a namespace */
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
/** Default suffix for building a config location for a namespace */
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
/**
* 这里是取得Resource位置的地方,使用默认的配置位/WEB-INF/applicationContext.xml。
*/
@Override
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
/**
* 这个加载过程在容器refresh()时启动
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// 对于XmlWebApplicationContext,当然是使用XmlBeanDefinitionReader来对BeanDefinition信息进行解析。
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 因为XmlWebApplicationContext是DefaultResourceLoader的子类,
// 所以这里同样使用DefaultResourceLoader来定位BeanDefinition。
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 钩子,允许子类自定义XmlBeanDefinitionReader的属性配置
initBeanDefinitionReader(beanDefinitionReader);
//这里使用定义好的XmlBeanDefinitionReader来载入BeanDefinition
loadBeanDefinitions(beanDefinitionReader);
}
/**
* 如果有多个BeanDefinition文件的定义,需逐个载入,都是通过reader来完成的
* 这个初始化过程是由refreshBeanFactory方法来完成的,这里只负责载入BeanDefinition。
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
public interface ServletContextListener extends EventListener {
//服务器启动的时候初始化入口
public void contextInitialized ( ServletContextEvent sce );
//服务器销毁的时候销毁入口
public void contextDestroyed ( ServletContextEvent sce );
}
ContextLoader 类是用来建立WEB环境的根上下文环境
第一步:
ContextLoaderListener 类的contextInitialized方法实现
/**
* Initialize the root web application context.
*/
public void contextInitialized(ServletContextEvent event) {
//这个方法实现的本意是提供一个占位符方法createContextLoader()给子类机会创建客户化的环境加载,
//但是,后来这个证明不是非常有用的,已经鼓励不再使用了,事实上,子类可以通过重写本方法达到同样的目的
this.contextLoader = createContextLoader();
//实际上是为了使用超类的默认实现
if (this.contextLoader == null) {
this.contextLoader = this;
}
//调用超类的加载根共享Web应用程序环境的默认实现
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
第二步:
ContextLoader 类的 initWebApplicationContext(ServletContext servletContext)代码
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//如果已经存在了根共享Web应用程序环境,则抛出异常提示客户
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");
}
//记录创建根Web应用程序环境的开始时间
long startTime = System.currentTimeMillis();
try {
//决定根Web应用程序环境是否存在父应用程序环境,一般是返回null
ApplicationContext parent = loadParentContext(servletContext);
//创建根Web应用程序环境,如果父环境存在则引用父环境,通常情况下父环境是不存在的 即parent=null
// 这是一个比较重要的方法,在第三步说明
this.context = createWebApplicationContext(servletContext, parent);
//把创建的根Web应用程序环境保存到Servlet环境中,每个派遣器Servlet加载的子环境会应用这个环境作为父环境
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) {
//如果线程和本类拥有不同的类加载器,则使用线程的类加载器作为关键在保存在一个映射对象里,保证析构时能拿到Web应用程序环境进行关闭操作
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);
//如果产生任何异常,则保存异常对象到Servlet环境里
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
//如果产生任何错误,则保存错误对象到Servlet环境里
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
第三步:
ContextLoader 类的 createWebApplicationContext(ServletContext, ApplicationContext) 代码
protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
//检测是否有配置的Web应用程序环境类(即存放bean的容器类),如果没有配置,则使用缺省的类XmlWebApplicationContext
// 例如在web.xml中配置
// <context-param>
// <param-name>contextClass</param-name>
// <param-value>org.springframework.web.context.support.GenericWebApplicationContext</param-value>
// </context-param>
Class<?> contextClass = determineContextClass(sc);
//如果配置的Web应用程序环境类不是可配置的Web应用程序环境的子类,则抛出异常,停止初始化
//若按上面的配置,这里会抛出异常,因为不是ConfigurableWebApplicationContext的子类
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
//实例化Web应用程序环境类
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//设置Web应用程序环境的ID
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
//如果 Servlet规范 <= 2.4,则使用web.xml里定义的应用程序名字定义Web应用程序名
String servletContextName = sc.getServletContextName();
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(servletContextName));
}
else {
// 如果Servlet规范是 2.5, 则使用配置的ContextPath定义Web应用程序名
try {
String contextPath = (String) ServletContext.class.getMethod("getContextPath").invoke(sc);
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(contextPath));
}
catch (Exception ex) {
throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex);
}
}
//如果父环境存在,则引用使用父环境
wac.setParent(parent);
//保存Servlet环境
wac.setServletContext(sc);
//设置根环境XML文件存放的位置
//这里如果以下配置容器启动不会报错,但若不配置,则会去寻找/WEB-INF/applicationContext.xml,若该文件不存在就则抛出FileNotFound异常
//<context-param>
// <param-name>contextConfigLocation</param-name>
// <param-value></param-value>
//</context-param>
wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));
//提供子类可互换Web应用程序环境的机会 占位符方法
customizeContext(sc, wac); //刷新Web应用程序环境以加载Bean定义,这里才是把我们XML里定义的bean放入容器的时候
wac.refresh();
return wac;
}