[转帖]SpringMVC深度探险(三) —— DispatcherServlet与初始化主线—1_Android, Python及开发编程讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Android, Python及开发编程讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3895 | 回复: 0   主题: [转帖]SpringMVC深度探险(三) —— DispatcherServlet与初始化主线—1        下一篇 
haili.yang
注册用户
等级:少校
经验:936
发帖:71
精华:1
注册:2012-12-24
状态:离线
发送短消息息给haili.yang 加好友    发送短消息息给haili.yang 发消息
发表于: IP:您无权察看 2012-12-26 15:08:40 | [全部帖] [楼主帖] 楼主

本文是专栏文章( SpringMVC深度探险)系列的文章之一,博客地址为: http://downpour.iteye.com/blog/1341459。 

在上一篇文章中,我们给出了构成SpringMVC应用程序的三要素以及三要素的设计过程。让我们来归纳一下整个设计过程中的一些要点: 

  • SpringMVC将Http处理流程抽象为一个又一个处理单元
  • SpringMVC定义了一系列组件(接口)与所有的处理单元对应起来
  • SpringMVC由DispatcherServlet贯穿始终,并将所有的组件串联起来
在整个过程中,组件和DispatcherServlet总是维持着一个相互支撑的关系: 

  • DispatcherServlet —— 串联起整个逻辑主线,是整个框架的心脏
  • 组件 —— 逻辑处理单元的程序化表示,起到承上启下的作用,是SpringMVC行为模式的实际承载者
在本系列接下来的两篇文章中,我们将分别讨论DispatcherServlet和组件的相关内容。本文讨论DispatcherServlet,而下一篇则重点分析组件。 

有关DispatcherServlet,我们想从构成DispatcherServlet的体系结构入手,再根据不同的逻辑主线分别加以分析,希望能够帮助读者整理出学习SpringMVC核心类的思路。 

DispatcherServlet的体系结构 

通过不同的角度来观察DispatcherServlet会得到不同的结论。我们在这里选取了三个不同的角度:运行特性、继承结构和数据结构。 

【运行主线】

从DispatcherServlet所实现的接口来看,DispatcherServlet的核心本质:是一个Servlet。这个结论似乎很幼稚,不过这个幼稚的结论却蕴含了一个对整个框架都至关重要的内在原则:Servlet可以根据其特性进行运行主线的划分。 

根据Servlet规范的定义,Servlet中的两大核心方法init方法和service方法,它们的运行时间和触发条件都截然不同: 

1. init方法 

在整个系统启动时运行,且只运行一次。因此,在init方法中我们往往会对整个应用程序进行初始化操作。这些初始化操作可能包括对容器(WebApplicationContext)的初始化、组件和外部资源的初始化等等。



2. service方法 

在整个系统运行的过程中处于侦听模式,侦听并处理所有的Web请求。因此,在service及其相关方法中,我们看到的则是对Http请求的处理流程。

因而在这里,Servlet的这一特性就被SpringMVC用于对不同的逻辑职责加以划分,从而形成两条互不相关的逻辑运行主线: 

  • 初始化主线 —— 负责对SpringMVC的运行要素进行初始化
  • Http请求处理主线 —— 负责对SpringMVC中的组件进行逻辑调度完成对Http请求的处理
对于一个MVC框架而言,运行主线的划分非常重要。因为只有弄清楚不同的运行主线,我们才能针对不同的运行主线采取不同的研究策略。而我们在这个系列中的绝大多数分析的切入点,也是围绕着不同的运行主线进行的。 

注:SpringMVC运行主线的划分依据是Servlet对象中不同方法的生命周期。事实上,��乎所有的MVC都是以此为依据来进行运行主线的划分。这进一步可以证明所有的MVC框架的核心基础还是Servlet规范,而设计理念的差异也导致了不同的框架走向了完全不同的发展道路。

【继承结构】

除了运行主线的划分以外,我们再关注一下DispatcherServlet的继承结构: 

北京联动北方科技有限公司

在这个继承结构中,我们可以看到DispatcherServlet在其继承树中包含了2个Spring的支持类:HttpServletBean和FrameworkServlet。我们分别来讨论一下这两个Spring的支持类在这里所起到的作用。 

HttpServletBean是Spring对于Servlet最低层次的抽象。在这一层抽象中,Spring会将这个Servlet视作是一个Spring的bean,并将init-param中的值作为bean的属性注入进来: 

Java代码   北京联动北方科技有限公司


public final void init() throws ServletException {
      if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
      }
      // Set bean properties from init parameters.
      try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
      }
      // Let subclasses do whatever initialization they like.
      initServletBean();
      if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
      }
}


从源码中,我们可以看到HttpServletBean利用了Servlet的init方法的执行特性,将一个普通的Servlet与Spring的容器联系在了一起。在这其中起到核心作用的代码是:BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);将当前的这个Servlet类转化为一个BeanWrapper,从而能够以Spring的方式来对init-param的值进行注入。BeanWrapper的相关知识属于Spring Framework的内容,我们在这里不做详细展开,读者可以具体参考HttpServletBean的注释获得更多的信息。 

FrameworkServlet则是在HttpServletBean的基础之上的进一步抽象。通过FrameworkServlet真正初始化了一个Spring的容器(WebApplicationContext),并引入到Servlet对象之中: 

Java代码   北京联动北方科技有限公司


protected final void initServletBean() throws ServletException {
      getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
      if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
      }
      long startTime = System.currentTimeMillis();
      try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
      } catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
      } catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
      }
      if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
            elapsedTime + " ms");
      }
}


上面的这段代码就是FrameworkServlet初始化的核心代码。从中我们可以看到这个FrameworkServlet将调用其内部的方法initWebApplicationContext()对Spring的容器(WebApplicationContext)进行初始化。同时,FrameworkServlet还暴露了与之通讯的结构可供子类调用: 

Java代码   北京联动北方科技有限公司


public abstract class FrameworkServlet extends HttpServletBean {
      private WebApplicationContext webApplicationContext;
      // 这里省略了其他所有的代码  
      public final WebApplicationContext getWebApplicationContext() {
            return this.webApplicationContext;
      }
}


我们在这里暂且不对Spring容器(WebApplicationContext)的初始化过程详加探查,稍后我们会讨论一些WebApplicationContext初始化过程中的配置选项。不过读者可以在这里体会到:FrameworkServlet在其内部初始化了一个Spring的容器(WebApplicationContext)并暴露了相关的操作接口,因而继承自FrameworkServlet的DispatcherServlet,也就直接拥有了与WebApplicationContext进行通信的能力。 

通过对DispatcherServlet继承结构的研究,我们可以明确: 

downpour 写道

结论 DispatcherServlet的继承体系架起了DispatcherServlet与Spring容器进行沟通的桥梁。



【数据结构】

在上一篇文章中,我们曾经提到过DispatcherServlet的数据结构: 

北京联动北方科技有限公司

我们可以把在上面这张图中所构成DispatcherServlet的数据结构主要分为两类(我们在这里用一根分割线将其分割开来): 

  • 配置参数 —— 控制SpringMVC组件的初始化行为方式
  • 核心组件 —— SpringMVC的核心逻辑处理组件
可以看到,这两类数据结构都与SpringMVC中的核心要素组件有关。因此,我们可以得出这样一个结论: 

downpour 写道

结论 组件是整个DispatcherServlet的灵魂所在:它不仅是初始化主线中的初始化对象,同样也是Http请求处理主线中的逻辑调度载体。



注:我们可以看到被我们划为配置参数的那些变量都是boolean类型的,它们将在DispatcherServlet的初始化主线中起到一定的作用,我们在之后会使用源码进行说明。而这些boolean值可以通过web.xml中的init-param值进行设定覆盖(这是由HttpServletBean的特性带来的)。

SpringMVC的运行体系 

DispatcherServlet继承结构和数据结构,实际上表述的是DispatcherServlet与另外两大要素之间的关系: 

  • 继承结构 —— DispatcherServlet与Spring容器(WebApplicationContext)之间的关系
  • 数据结构 —— DispatcherServlet��组件之间的关系
所以,其实我们可以这么说:SpringMVC的整个运行体系,是由DispatcherServlet、组件和容器这三者共同构成的。 

在这个运行体系中,DispatcherServlet是逻辑处理的调度中心,组件则是被调度的操作对象。而容器在这里所起到的作用,是协助DispatcherServlet更好地对组件进行管理。这就相当于一个工厂招了一大批的工人,并把工人划分到一个统一的工作车间而便于管理。在工厂要进行生产活动时,只需要从工作车间把工人分派到相应的生产流水线上即可。 

笔者在这里引用Spring官方reference中的一幅图,对三者之间的关系进行简单的描述: 

北京联动北方科技有限公司

注:在这幅图中,我们除了看到在图的左半边DispatcherServlet、组件和容器这三者之间的调用关系以外,还可以看到SpringMVC的运行体系与其它运行体系之间存在着关系。有关这一点,我们在之后的讨论中会详细展开。

既然是三个元素之间的关系表述,我们必须以两两关系的形式进行归纳: 

  • DispatcherServlet - 容器 —— DispatcherServlet对容器进行初始化
  • 容器 - 组件 —— 容器对组件进行全局管理
  • DispatcherServlet - 组件 —— DispatcherServlet对组件进行逻辑调用
值得注意的是,在上面这幅图中,三大元素之间的两两关系其实表现得并不明显,尤其是“容器 - 组件”和“DispatcherServlet - 组件”之间的关系。这主要是由于Spring官方reference所给出的这幅图是一个静态的关系表述,如果从动态的观点来对整个过程加以审视,我们就不得不将SpringMVC的运行体系与之前所提到的运行主线联系在一起,看看这些元素在不同的逻辑主线中所起到的作用。 

接下来,我们就分别看看DispatcherServlet的两条运行主线。 

DispatcherServlet的初始化主线 

对于DispatcherServlet的初始化主线,我们首先应该明确几个基本观点: 

  • 初始化主线的驱动要素 —— servlet中的init方法
  • 初始化主线的执行次序 —— HttpServletBean -> FrameworkServlet -> DispatcherServlet
  • 初始化主线的操作对象 —— Spring容器(WebApplicationContext)和组件
这三个基本观点,可以说是我们对之前所有讨论的一个小结。明确了这些内容,我们就可以更加深入地看看DispatcherServlet初始化主线的过程: 

北京联动北方科技有限公司

在这幅图中,我们站在一个动态的角度将DispatcherServlet、容器(WebApplicationContext)和组件这三者之间的关系表述出来,同时给出了这三者之间的运行顺序和逻辑过程。读者或许对其中的绝大多数细节还很陌生,甚至有一种无从下手的感觉。这没有关系,大家可以首先抓住图中的执行线,回忆一下之前有关DispatcherServlet的继承结构和数据结构的内容。接下来,我们就图中的内容逐一进行解释。 

【WebApplicationContext的初始化】

之前我们讨论了DispatcherServlet对于WebApplicationContext的初始化是在FrameworkServlet中完成的,不过我们并没有细究其中的细节。在默认情况下,这个初始化过程是由web.xml中的入口程序配置所驱动的: 

Xml代码   北京联动北方科技有限公司


<!-- Processes application requests -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/**</url-pattern>
</servlet-mapping>


我们已经不止一次提到过这段配置,不过在这之前都没有对这段配置做过什么很详细的分析。事实上,这段入口程序的配置中隐藏了SpringMVC的两大要素(核心分发器Dispatcher和核心配置文件[servlet-name]-servlet.xml)之间的关系表述: 

downpour 写道

在默认情况下,web.xml配置节点中<servlet-name>的值就是建立起核心分发器DispatcherServlet与核心配置文件之间联系���桥梁。DispatcherServlet在初始化时会加载位置在/WEB-INF/[servlet-name]-servlet.xml的配置文件作为SpringMVC的核心配置。



SpringMVC在这里采用了一个“命名约定”的方法进行关系映射,这种方法很廉价也很管用。以上面的配置为例,我们就必须在/WEB-INF/目录下,放一个名为dispatcher-servlet.xml的Spring配置文件作为SpringMVC的核心配置用以指定SpringMVC的基本组件声明定义。 

这看上去似乎有一点别扭,因为在实际项目中,我们通常喜欢把配置文件放在classpath下,并使用不同的package进行区分。例如,在基于Maven的项目结构中,所有的配置文件应置于src/main/resources目录下,这样才比较符合配置文件统一化管理的最佳实践。 

于是,Spring提供了一个初始化的配置选项,通过指定contextConfigLocation选项来自定义SpringMVC核心配置文件的位置: 

Xml代码   北京联动北方科技有限公司


<!-- Processes application requests -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:web/applicationContext-dispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>


这样一来,DispatcherServlet在初始化时,就会自动加载在classpath下,web这个package下名为applicationContext-dispatcherServlet.xml的文件作为其核心配置并用以初始化容器(WebApplicationContext)。 

当然,这只是DispatcherServlet在进行WebApplicationContext初始化过程中的配置选项之一。我们可以在Spring的官方reference中找到相应的配置选项,有兴趣的读者可以参照reference的说明进行尝试: 

北京联动北方科技有限公司

所有的这些配置选项,实际上都是为了让DispatcherServlet对WebApplicationContext的初始化过程显得更加自然。不过这只是完成了容器(WebApplicationContext)的构建工作,那么容器所管理的那些组件,又是如何进行初始化的呢? 

downpour 写道

结论 SpringMVC核心配置文件中所有的bean定义,就是SpringMVC的组件定义,也是DispatcherServlet在初始化容器(WebApplicationContext)时,所要进行初始化的组件。




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论