基于版本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里面做的事情非常多,下一节再讲