您的位置:首页 > 编程语言 > Java开发

深入剖析Spring Web源码(七) - DispatcherServlet的实现 - 根共享环境的加载/其他Servlet

2010-09-17 20:30 826 查看

1.1.3 根共享环境的加载

上一节中我们在分析框架Sevlet是如何初始化Web应用程序环境的时候得知,一个Servlet拥有一个专用的子环境,但是这个子环境可以而且通常引用一个根共享环境,这个根共享环境是通过Servlet环境监听器加载的。也就是说,当一个Servlet环境,也就是一个应用程序被容器加载时,监听器通过监听这个初始化事件初始化根共享Web应用程序,而当一个Servlet环境析构时,监听器功过监听这个析构时间析构共享的Web应用程序环境。

下面是整个根共享环境加载的类图,



图表 4‑9

我们可以看到,类环境加载监听器类实现了Servlet规范中定义的Servlet环境监听器用以处理初始化事件和析构事件。而真正的根共享环境的创建的实现是环境加载类中实现的。在环境加载类中,通过Servlet初始化参数配置的根共享环境位置加载Web应用程序环境,并且将这个环境以ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为关键字保存在Servlet环境中,这个根共享环境在Servlet加载专用子环境中被引用作为父环境。

public void contextInitialized(ServletContextEvent event) {
//这个方法实现的本意是提供一个占位符方法createContextLoader()给子类机会创建客户化的环境加载,但是,后来这个证明不是非常有用的,已经鼓励不再使用了,事实上,子类可以通过重写本方法达到同样的目的
this.contextLoader = createContextLoader();

//没有子类实现createContextLoader()占位符 方法,则使用超类的缺省实现,超类 就是环境 加载类
if (this.contextLoader == null) {
//实际上是为了使用超类的默认实现
this.contextLoader = this;
}

//调用超类的加载根共享Web应用程序环境的默认实现
this.contextLoader.initWebApplicationContext(event.getServletContext());
}

public void contextDestroyed(ServletContextEvent event) {
//如果环境加载存在,那么关闭环境加载的Web应用程序环境
if (this.contextLoader != null) {
this.contextLoader.closeWebApplicationContext(event.getServletContext());
}

//清除保存在Servlet环境中的任何可释放的Bean
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}

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应用程序环境是否存在父应用程序环境
ApplicationContext parent = loadParentContext(servletContext);

//创建根Web应用程序环境,如果父环境存在则引用父环境,通常情况下父环境是不存在的
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;
}
}

protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
//取得配置的Web应用程序环境类,如果没有配置,则使用缺省的类XmlWebApplicationContext
Class<?> contextClass = determineContextClass(sc);

//如果配置的Web应用程序环境类不是可配置的Web应用程序环境的子类,则抛出异常,停止初始化
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();

//设置ID
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);

//设置ID
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(contextPath));
}
catch (Exception ex) {
//如果Servlet规范是2.5,但是不能取得ContextPath,抛出异常
throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex);
}
}

//如果父环境存在,则引用使用父环境
wac.setParent(parent);

//保存Servlet环境
wac.setServletContext(sc);

//设置环境的位置
wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));

//提供子类可互换Web应用程序环境的机会
customizeContext(sc, wac);

//刷新Web应用程序环境以加载Bean定义
wac.refresh();
return wac;
}

protected Class<?> determineContextClass(ServletContext servletContext) {
//首先检查是否初始化参数中定义了Web应用程序环境的类名
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
//如果初始化参数中定义了Web应用程序环境的类名,加载定义的类名
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
//如果初始化参数中定义了Web应用程序环境的类名,加载缺省策略中定义的类名,缺省策略保存在ContextLoader.properties文件里
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
//加载缺省策略中定义的类名
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}

public void closeWebApplicationContext(ServletContext servletContext) {
servletContext.log("Closing Spring root WebApplicationContext");
try {
//如果是可配置的Web应用程序环境
if (this.context instanceof ConfigurableWebApplicationContext) {
//关闭可配置的Web应用程序环境
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
//取得当前线程的类加载器
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
//如果当前线程和本类的公用一个类加载器,则清空静态变量引用
currentContext = null;
}
else if (ccl != null) {
//否则根据线程的类加载器移除保存的Web应用程序环境
currentContextPerThread.remove(ccl);
}

//移除Servlet环境中的Web应用程序环境的引用
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

//如果父环境存在则释放父环境
if (this.parentContextRef != null) {
this.parentContextRef.release();
}
}
}


事实上,根共享环境的加载时同样可以加载一个父环境。尽管这种情况是不常见的,但是Spring Web MVC提供了这样的扩展性。在Servlet初始化参数中可以配置一个Bean工厂路径(locatorFactorySelector),这个Bean工厂路径会被Bean工厂定位器所加载,Bean工厂定位器会在这个Bean工厂中查找以另外一个Servlet参数(parentContextKey)为名字的Bean工厂对象,最后得到的Bean工厂对象则是根共享环境的父环境。如果在初始化参数中没有配置Bean工厂路径,则用缺省的Bean工厂路径classpath*:beanRefFactory.xml。

protected ApplicationContext loadParentContext(ServletContext servletContext) {
ApplicationContext parentContext = null;

//取得Web.xml初始化参数配置中对LOCATOR_FACTORY_SELECTOR_PARAM的配置串,这是Bean工厂定位器使用的Bean工厂的路径,如果这个值没有配置,则使用缺省的classpath*:beanRefFactory.xml
String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);

//取得Web.xml初始化参数配置中对LOCATOR_FACTORY_KEY_PARAM的配置串,这是用来取得Bean工厂的关键字
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);

if (parentContextKey != null) {
//locatorFactorySelector如果为空,则使用缺省值classpath*:beanRefFactory.xml初始化Bean工厂定位器
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isDebugEnabled()) {
logger.debug("Getting parent context definition: using parent context key of '" +
parentContextKey + "' with BeanFactoryLocator");
}

//Bean工厂定位器从配置的Bean工厂中找到制定关键字(参数LOCATOR_FACTORY_KEY_PARAM的值) 的工厂
this.parentContextRef = locator.useBeanFactory(parentContextKey);

//进而取得一个应用程序环境,这个应用程序环境作为根共享应用程序环境的父环境
parentContext = (ApplicationContext) this.parentContextRef.getFactory();
}

return parentContext;
}


Bean工厂定位器的实现中,加载了一个指定的Bean引用工厂,然后在加载的Bean引用工厂中查找指定名字的Bean工厂对象,这个Bean工厂对象会被返回,作为根共享环境的父环境。这些实现属于Spring 环境项目范围,以下给出序列图,将不在这里做代码注释。



图表 4‑10

根据上面的分析,我们发现Spring Web MVC是依赖于Spring环境的定义的,而每一个Spring环境可以最多有一个父环境的引用。这些特点同样应用到了Spring Web MVC的体系结构里。下面我们总结以下Spring Web MVC里面环境的三个层次。其中Servlet专用跟环境和根共享主环境在同一个层次。

Servlet专用子环境

加载组件:派遣器Servlet(框架Servlet)
配置路径:Servlet初始化参数contextConfigLocation指定的路径
缺省路径: WEB-INF/[servlet_name] -servlet.xml
保存位置:在框架Servlet对象内部,也以关键字FrameworkServlet全类名.CONTEXT.Servlet名保存在Servlet环境里

Servlet专用根环境

这是一个需要定制实现的组件,组件实现需要把加载的环境以某个关键字保存在Servlet环境里。

这样,如果在某个派遣器Servlet初始化参数contextAttribute指定这个关键字, Servlet专用子环境会引用这个加载的专用根环境作为父环境。

根共享主环境

加载组件:环境加载监听器
配置路径:Servlet环境初始化参数contextConfigLocation指定的路径
缺省路径: 没有缺省路径
保存位置:WebApplicationContext全类名.ROOT

根共享环境主环境的父环境

加载组件:环境加载监听器和Bean工厂定位器
配置路径:Servlet环境初始化参数locatorFactorySelector指定Bean工厂定位器使用的给BeanFactory,Servlet环境初始化参数parentContextKey指定Bean工厂定位器用于查找BeanFactory的关键字
缺省路径: parentContextKey的缺省路径是classpath*:beanRefFactory.xml,如果parentContextKey没有制定,则超找所有ApplicationContext的子类实现
保存位置:WebApplicationContext全类名.ROOT

除了Servlet专用子环境,其他的父环境都是可选的。根据上面层次的组合,一共有4种环境配置,如下,

1. 单个Servlet专用子环境



图表 4‑11
2. Servlet专用子环境引用到Servlet专用根环境



图表 4‑12
3. Servlet专用子环境引用到根共享主环境



图表 4‑13

4. Servlet专用子环境引用到根共享主环境及其父环境



图表 4‑14
其中,配置1和配置3是我们在开发中经常使用到的。但是在业务逻辑更复杂的情况下,我们可以选择配置2和配置4。配置2能够使多个Servlet共享一个根环境。配置4能使共享的跟环境通过一个Serlvet配置参数转换它的父环境。

1.1.4 其他Servlet

ResourceServlet是用于存取Web应用程序的内部资源的。它也继承自HttpServletBean,所以能够自动的将Servlet的初始化参数作为属性值来初始化Servlet对象。它改写了HTTP Servlet的Get方法来处理HTTP对资源的请求。

HttpRequestHandlerServlet是用来直接将一个HTTP请求转发给HttpRequestHandler。

ViewRenderSevlet是用来与Portlet进行集成的Sevlet。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐