基于版本4.1.7.RELEASE
ContextLoader :应用root application context初始化的实际执行着,被ContextLoaderListener调用
构造函数:
public ContextLoader() {
}
根据servlet配置中的contextClass和contextConfigLocation来创建web application context,在其子类ContextLoaderListener被申明的时候会调用默认的构造函数。
带参数的构造函数:
public ContextLoader(WebApplicationContext context) {
this.context = context;
}
context作用同ContextLoaderListener中说明一样,只是ContextLoaderListener调用super(context)的时候会调用到此方法中,然后将context赋值给类属性,查看类属性的申明:
/**
* The root WebApplicationContext instance that this loader manages.
*/
private WebApplicationContext context;
即可明白通过构造函数设置的是root WebApplicationContext
下面来看在ContextLoaderListener的初始化事件通知中所调用的initWebApplicationContext方法
/**
* 通过参数servletContext初始化WebApplicationContext,使用构造时提供的WebApplicationContext或者根据contextClass和contextConfigL * ocation(在web.xml定义)创建一个新的WebApplicationContext
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//防止重复初始化
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException;
}
try {
//如果当前的rootWebApplicationContext为空 则创建一个,如果不为空有可能是构造时传进来的
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//判断是否active的条件是,至少被refresh了1次并且没有被关闭。refresh可以理解为同步配置数据
if (!cwac.isActive()) {
// context没有被refresh
if (cwac.getParent() == null) {
// context没有明确的父容器 读取父容器
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//配置和刷新context
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将context保存到WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//这里根据classloader来判断当前线程是否是加载ContextLoader的线程。
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
return this.context;
}
catch (RuntimeException ex) {
}
catch (Error err) {
}
}
这里有几个疑问,parent是什么,还有线程和context的对应关系是用来做什么的?
对于parent可以看loadParentContext这个方法:
protected ApplicationContext loadParentContext(ServletContext servletContext) {
ApplicationContext parentContext = null;
//获取web.xml配置文件中指定的locatorFactorySelector,如果没有配置这个选项,默认指向“class path*:beanRefContext.xml”
String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
//获取web.xml配置文件中指定的parentContentKey,这个key指向一个被加载完成的BeanFactory
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
if (parentContextKey != null) {
//获取beanFactory定位器
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
//根据定位器查找到指定的BeanFactory,该beanFactory加载了beanRefContext.xml(默认情况下),在设置配置文件路径完成后调用了自身的refresh,所以已经将配置文件中定义的内容加载完成了,而完成加载的ApplicationContext就是这里要返回的parentContext
this.parentContextRef = locator.useBeanFactory(parentContextKey);
parentContext = (ApplicationContext) this.parentContextRef.getFactory();
}
return parentContext;
}
这个方法一般是在EJB或者EAR需要共享容器的时候使用,对于一般的WEB型应用,由于不配置locatorFactorySelector,所以这个方法实际上并没有运行。
线程和context的对应关系存放在currentContextPerThread中,该变量的类型是Map<ClassLoader,WebApplicationContext>,至于为什么这么存,
在closeWebApplicationContext中我们可以一窥端倪
public void closeWebApplicationContext(ServletContext servletContext) {
try {
//关闭当前的Context,关闭过程中会产生关闭事件通知,销毁当前Context关联的bean和beanFactory,回调子类的onClose方法,active设置成false。
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
//释放ClassLoader和Context对应列表中当前ClassLoader对应的Context
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = null;
}
else if (ccl != null) {
currentContextPerThread.remove(ccl);
}
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (this.parentContextRef != null) {
//释放对parent的引用
this.parentContextRef.release();
}
}
}
currentContextPerThread是一个静态类型的引用,目的就是保存不同的ClassLoader对应的Context以防止在销毁Context时出现错误关闭的情况。
在上面的处理过程中,configureAndRefreshWebApplicationContext做了以下几件事
1 设置ID,把ServletContext中的ID赋值给WebApplicationContext,如果没有,则设置默认值
2 根据contextConfigLocation参数(web.xml)指定的地址,找到spring application的xml文件
3 初始化环境变量
4 根据定制的ApplicationContextInitializer初始化Context,这些定制器必须实现ApplicationContextInitializer 并且在contextInitializerClasses(web.xml)申明出来
5 刷新调用refresh方法
由此我们可以看出,如果要对容器做定制化修改,根据第四步来编写自定义类即可。
那么,走到这里,整个初始化就剩下refresh了,refresh里面做的事情非常多,下一节再讲