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

本文主要是介绍深入剖析Spring Web源码(七) - DispatcherServlet的实现 - 根共享环境的加载/其他Servlet,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.1.3   根共享环境的加载

 

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

 

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

 

 

图表 4‑9

 

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

 

[java] view plain copy print ?
  1. public void contextInitialized(ServletContextEvent event) {  
  2.     //这个方法实现的本意是提供一个占位符方法createContextLoader()给子类机会创建客户化的环境加载,但是,后来这个证明不是非常有用的,已经鼓励不再使用了,事实上,子类可以通过重写本方法达到同样的目的  
  3.     this.contextLoader = createContextLoader();  
  4.       
  5.     //没有子类实现createContextLoader()占位符 方法,则使用超类的缺省实现,超类 就是环境 加载类  
  6.     if (this.contextLoader == null) {  
  7.         //实际上是为了使用超类的默认实现  
  8.         this.contextLoader = this;  
  9.     }  
  10.       
  11.     //调用超类的加载根共享Web应用程序环境的默认实现  
  12.     this.contextLoader.initWebApplicationContext(event.getServletContext());  
  13. }  
  14.   
  15. public void contextDestroyed(ServletContextEvent event) {  
  16.     //如果环境加载存在,那么关闭环境加载的Web应用程序环境  
  17.     if (this.contextLoader != null) {  
  18.         this.contextLoader.closeWebApplicationContext(event.getServletContext());  
  19.     }  
  20.       
  21.     //清除保存在Servlet环境中的任何可释放的Bean  
  22.     ContextCleanupListener.cleanupAttributes(event.getServletContext());  
  23. }  
  24.   
  25. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {  
  26.     //如果已经存在了根共享Web应用程序环境,则抛出异常提示客户  
  27.     if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {  
  28.         throw new IllegalStateException(  
  29.                 "Cannot initialize context because there is already a root application context present - " +  
  30.                 "check whether you have multiple ContextLoader* definitions in your web.xml!");  
  31.     }  
  32.   
  33.     Log logger = LogFactory.getLog(ContextLoader.class);  
  34.     servletContext.log("Initializing Spring root WebApplicationContext");  
  35.     if (logger.isInfoEnabled()) {  
  36.         logger.info("Root WebApplicationContext: initialization started");  
  37.     }  
  38.       
  39.     //记录创建根Web应用程序环境的开始时间  
  40.     long startTime = System.currentTimeMillis();  
  41.   
  42.     try {  
  43.         //决定根Web应用程序环境是否存在父应用程序环境  
  44.         ApplicationContext parent = loadParentContext(servletContext);  
  45.   
  46.         //创建根Web应用程序环境,如果父环境存在则引用父环境,通常情况下父环境是不存在的  
  47.         this.context = createWebApplicationContext(servletContext, parent);  
  48.   
  49.         //把创建的根Web应用程序环境保存到Servlet环境中,每个派遣器Servlet加载的子环境会应用这个环境作为父环境  
  50.         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);  
  51.   
  52.   
  53.         //取得线程的类加载器  
  54.         ClassLoader ccl = Thread.currentThread().getContextClassLoader();  
  55.         if (ccl == ContextLoader.class.getClassLoader()) {  
  56.             //如果线程和本类拥有相同的类加载器,则使用静态变量保存即可,因为同一类加载器加载同一份静态变量  
  57.             currentContext = this.context;  
  58.         }  
  59.         else if (ccl != null) {  
  60.             //如果线程和本类拥有不同的类加载器,则使用线程的类加载器作为关键在保存在一个映射对象里,保证析构时能拿到Web应用程序环境进行关闭操作  
  61.             currentContextPerThread.put(ccl, this.context);  
  62.         }  
  63.   
  64.         if (logger.isDebugEnabled()) {  
  65.             logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +  
  66.                     WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");  
  67.         }  
  68.         if (logger.isInfoEnabled()) {  
  69.             long elapsedTime = System.currentTimeMillis() - startTime;  
  70.             logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");  
  71.         }  
  72.   
  73.         return this.context;  
  74.     }  
  75.     catch (RuntimeException ex) {  
  76.         logger.error("Context initialization failed", ex);  
  77.         //如果产生任何异常,则保存异常对象到Servlet环境里  
  78.         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);  
  79.         throw ex;  
  80.     }  
  81.     catch (Error err) {  
  82.         logger.error("Context initialization failed", err);  
  83.         //如果产生任何错误,则保存错误对象到Servlet环境里  
  84.         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);  
  85.         throw err;  
  86.     }  
  87. }  
  88.   
  89. protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {  
  90.     //取得配置的Web应用程序环境类,如果没有配置,则使用缺省的类XmlWebApplicationContext  
  91.     Class<?> contextClass = determineContextClass(sc);  
  92.       
  93.     //如果配置的Web应用程序环境类不是可配置的Web应用程序环境的子类,则抛出异常,停止初始化  
  94.     if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {  
  95.         throw new ApplicationContextException("Custom context class [" + contextClass.getName() +  
  96.                 "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");  
  97.     }  
  98.       
  99.     //否则实例化Web应用程序环境类  
  100.     ConfigurableWebApplicationContext wac =  
  101.             (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);  
  102.   
  103.     //设置Web应用程序环境的ID  
  104.     if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {  
  105.         // 如果 Servlet规范 <= 2.4,则使用web.xml里定义的应用程序名字定义Web应用程序名  
  106.         String servletContextName = sc.getServletContextName();  
  107.           
  108.         //设置ID  
  109.         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +  
  110.                 ObjectUtils.getDisplayString(servletContextName));  
  111.     }  
  112.     else {  
  113.         // 如果Servlet规范是 2.5, 则使用配置的ContextPath定义Web应用程序名  
  114.         try {  
  115.             String contextPath = (String) ServletContext.class.getMethod("getContextPath").invoke(sc);  
  116.               
  117.             //设置ID  
  118.             wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +  
  119.                     ObjectUtils.getDisplayString(contextPath));  
  120.         }  
  121.         catch (Exception ex) {  
  122.             //如果Servlet规范是2.5,但是不能取得ContextPath,抛出异常  
  123.             throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex);  
  124.         }  
  125.     }  
  126.   
  127.     //如果父环境存在,则引用使用父环境  
  128.     wac.setParent(parent);  
  129.       
  130.     //保存Servlet环境  
  131.     wac.setServletContext(sc);  
  132.       
  133.     //设置环境的位置  
  134.     wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));  
  135.       
  136.     //提供子类可互换Web应用程序环境的机会  
  137.     customizeContext(sc, wac);  
  138.       
  139.     //刷新Web应用程序环境以加载Bean定义  
  140.     wac.refresh();  
  141.     return wac;  
  142. }  
  143.   
  144. protected Class<?> determineContextClass(ServletContext servletContext) {  
  145.     //首先检查是否初始化参数中定义了Web应用程序环境的类名  
  146.     String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);  
  147.     if (contextClassName != null) {  
  148.         try {  
  149.             //如果初始化参数中定义了Web应用程序环境的类名,加载定义的类名  
  150.             return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());  
  151.         }  
  152.         catch (ClassNotFoundException ex) {  
  153.             throw new ApplicationContextException(  
  154.                     "Failed to load custom context class [" + contextClassName + "]", ex);  
  155.         }  
  156.     }  
  157.     else {  
  158.         //如果初始化参数中定义了Web应用程序环境的类名,加载缺省策略中定义的类名,缺省策略保存在ContextLoader.properties文件里  
  159.         contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());  
  160.         try {  
  161.             //加载缺省策略中定义的类名  
  162.             return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());  
  163.         }  
  164.         catch (ClassNotFoundException ex) {  
  165.             throw new ApplicationContextException(  
  166.                     "Failed to load default context class [" + contextClassName + "]", ex);  
  167.         }  
  168.     }  
  169. }  
  170.   
  171. public void closeWebApplicationContext(ServletContext servletContext) {  
  172.     servletContext.log("Closing Spring root WebApplicationContext");  
  173.     try {  
  174.         //如果是可配置的Web应用程序环境  
  175.         if (this.context instanceof ConfigurableWebApplicationContext) {  
  176.             //关闭可配置的Web应用程序环境  
  177.             ((ConfigurableWebApplicationContext) this.context).close();  
  178.         }  
  179.     }  
  180.     finally {  
  181.         //取得当前线程的类加载器  
  182.         ClassLoader ccl = Thread.currentThread().getContextClassLoader();  
  183.         if (ccl == ContextLoader.class.getClassLoader()) {  
  184.             //如果当前线程和本类的公用一个类加载器,则清空静态变量引用  
  185.             currentContext = null;  
  186.         }  
  187.         else if (ccl != null) {  
  188.             //否则根据线程的类加载器移除保存的Web应用程序环境  
  189.             currentContextPerThread.remove(ccl);  
  190.         }  
  191.           
  192.         //移除Servlet环境中的Web应用程序环境的引用  
  193.         servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);  
  194.           
  195.         //如果父环境存在则释放父环境  
  196.         if (this.parentContextRef != null) {  
  197.             this.parentContextRef.release();  
  198.         }  
  199.     }  
  200. }  
  

 

 

 

 

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

 

[java] view plain copy print ?
  1. protected ApplicationContext loadParentContext(ServletContext servletContext) {  
  2.     ApplicationContext parentContext = null;  
  3.       
  4.     //取得Web.xml初始化参数配置中对LOCATOR_FACTORY_SELECTOR_PARAM的配置串,这是Bean工厂定位器使用的Bean工厂的路径,如果这个值没有配置,则使用缺省的classpath*:beanRefFactory.xml  
  5.     String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);  
  6.       
  7.     //取得Web.xml初始化参数配置中对LOCATOR_FACTORY_KEY_PARAM的配置串,这是用来取得Bean工厂的关键字  
  8.     String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);  
  9.   
  10.     if (parentContextKey != null) {  
  11.         //locatorFactorySelector如果为空,则使用缺省值classpath*:beanRefFactory.xml初始化Bean工厂定位器  
  12.         BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);  
  13.         Log logger = LogFactory.getLog(ContextLoader.class);  
  14.         if (logger.isDebugEnabled()) {  
  15.             logger.debug("Getting parent context definition: using parent context key of '" +  
  16.                     parentContextKey + "' with BeanFactoryLocator");  
  17.         }  
  18.           
  19.   
  20.         //Bean工厂定位器从配置的Bean工厂中找到制定关键字(参数LOCATOR_FACTORY_KEY_PARAM的值) 的工厂  
  21.         this.parentContextRef = locator.useBeanFactory(parentContextKey);  
  22.           
  23.         //进而取得一个应用程序环境,这个应用程序环境作为根共享应用程序环境的父环境  
  24.         parentContext = (ApplicationContext) this.parentContextRef.getFactory();  
  25.     }  
  26.   
  27.     return parentContext;  
  28. }  

 

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。

欢迎加入我的QQ交流群425783133

这篇关于深入剖析Spring Web源码(七) - DispatcherServlet的实现 - 根共享环境的加载/其他Servlet的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/928370

相关文章

Python实现精确小数计算的完全指南

《Python实现精确小数计算的完全指南》在金融计算、科学实验和工程领域,浮点数精度问题一直是开发者面临的重大挑战,本文将深入解析Python精确小数计算技术体系,感兴趣的小伙伴可以了解一下... 目录引言:小数精度问题的核心挑战一、浮点数精度问题分析1.1 浮点数精度陷阱1.2 浮点数误差来源二、基础解决

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

Spring 中的切面与事务结合使用完整示例

《Spring中的切面与事务结合使用完整示例》本文给大家介绍Spring中的切面与事务结合使用完整示例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录 一、前置知识:Spring AOP 与 事务的关系 事务本质上就是一个“切面”二、核心组件三、完

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

Java实现远程执行Shell指令

《Java实现远程执行Shell指令》文章介绍使用JSch在SpringBoot项目中实现远程Shell操作,涵盖环境配置、依赖引入及工具类编写,详解分号和双与号执行多指令的区别... 目录软硬件环境说明编写执行Shell指令的工具类总结jsch(Java Secure Channel)是SSH2的一个纯J

使用Python实现Word文档的自动化对比方案

《使用Python实现Word文档的自动化对比方案》我们经常需要比较两个Word文档的版本差异,无论是合同修订、论文修改还是代码文档更新,人工比对不仅效率低下,还容易遗漏关键改动,下面通过一个实际案例... 目录引言一、使用python-docx库解析文档结构二、使用difflib进行差异比对三、高级对比方

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达