当前位置: 代码迷 >> Web前端 >> 《Struts2技术内情》 新书部分篇章连载(三)―― 多视角透析Struts2
  详细解决方案

《Struts2技术内情》 新书部分篇章连载(三)―― 多视角透析Struts2

热度:117   发布时间:2012-11-22 00:16:41.0
《Struts2技术内幕》 新书部分篇章连载(三)―― 多视角透析Struts2
3.3 多视角透析Struts2

Struts2的外部环境并不复杂,因为其核心内容非常明确:探究Struts2运行时所必须的基本要素。我们对Struts2的运行环境和Struts2所依赖的核心技术的讲解,更多是为了让读者了解Struts2能够顺利运行的条件。

在明确了Struts2的外部环境之后,我们讨论的话题就将转向Struts2本身。在本节中,我们将从宏观和微观这两个不同的视角,阐述Struts2的总体架构和内部元素构成,以此揭开Struts2的神秘面纱。

3.3.1透视镜 ―― Struts2的宏观视图

Struts2的宏观视图是指站在整个框架的角度,程序的运行可以划分为哪些逻辑运行主线。对于一个框架的逻辑运行主线的研究,也是我们分析一个框架最为重要的切入点。而这一切入点,就位于Struts2的核心入口程序之中。

3.3.1.1 Struts2的核心入口程序

Struts2的核心入口程序,从功能上讲必须能够处理Http请求,这是表示层框架的基本要求。为了达到这一要求,Struts2毫无例外地遵循了Servlet标准,通过实现标准的Filter接口来进行Http请求的处理。我们通过在web.xml中指定这个实现类,就可以将Struts2框架引入到应用中来,如代码清单3-1所示:

<filter>
    <filter-name>struts</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>struts</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>


如果打开StrutsPrepareAndExecuteFilter的源码,我们可以发现它只是一个实现了Filter接口的实现类。其方法列表如图3-3所示:



根据Servlet标准中Filter的生命周期的相关知识,我们知道Filter中所定义的方法,具有完全不同的执行时间段和生命周期,它们的执行互不影响,没有交叉。而Filter的生命周期也成为我们对整个Struts2进行运行逻辑主线划分的主要依据:

  • 第一条主线 ―― Struts2的初始化 ―― init方法驱动执行
  • 第二条主线 ―― Struts2处理Http请求 ―― doFilter方法驱动执行

在这里,我们首先不对StrutsPrepareAndExecuteFilter的源码做深入的探讨,不过我们可以先把这个入口程序的基本结构和功能结合我们对Struts2划分的两条逻辑主线,用示意图图的形式表达出来,从而帮助读者对入口程序的运行逻辑主线有个初步的认识,示意图如图3-4所示:



从图中,我们可以清晰地看到Struts2的两条逻辑主线之间有一条分隔符分开。不同的逻辑主线,他们之间完全没有交叉,驱动他们执行的时间节点和触发条件都不同。因而,我们日后对Struts2运行逻辑的分析,也将围绕着这两条主线分别展开。
Struts2入口程序的示意图,可以看作是对Web容器黑盒模型的第一层细化,读者在这里应该更多关心程序运行的基本方向,其内部细节,有待我们在之后的章节为大家一一解开其中的奥秘。

3.3.1.2 Struts2的初始化主线

Struts2的初始化主线发生在Web应用程序启动之初,由入口程序的init方法驱动执行完成。这条运行主线主要特点有:

仅在Web应用启动时执行一次

由于这条主线由Filter中的init方法驱动执行,执行完毕后,该主线结束。也就是说,这条主线本身不参与后任何的Http请求的处理过程,无论Struts2之后再收到多少Http请求,这条主线都不会重复执行。

Init方法的执行失败将导致整个Web应用启动失败

如果在init方法执行的过程中发生异常,整个Web应用将无法启动。这个特点从框架规范的角度规定了我们必须确保初始化过程的顺利执行。因为在这个过程中,所以框架内部定义的元素将被初始化,并支撑起整个Struts2进行Http处理的过程。

这两大特点本身其实来源于Filter这个Servlet规范的基本运行特性。然而,这两大特点却也为应用程序在框架的基础之上进行逻辑扩展提供了理论上的指导意见。在之后有关如何扩展框架的话题讨论中,我们可以看到所有的扩展方案都将基于这两大特点进行设计。

那么,Struts2的初始化主线到底做了点什么呢?对应于Struts2初始化的运行特点,Struts2的初始化主线也有两大主要内容:

框架元素的初始化工作

这一初始化工作包含了对框架内部的许多内置对象的创建和缓存。我们发现,对于框架初始化工作的基本要求,就是在整个框架的运行过程中仅被执行一次,这正好符合这条主线的基本特点。

控制框架运行的必要条件

框架的可扩展特性保证了我们可以在应用层面对框架的运行参数和执行模式进行定制化,而框架则有必要对这种定制化进行正确性校验。当这种校验失败时,Web应用的启动会失败。这也就是Struts2在框架级别所能够提供的运行期的检查。

初始化主线贯穿了Struts2对其内置对象的创建和缓存的过程,这一过程相当于把整个Struts2作为一个框架的运行环境完整地创建出来。这条主线的顺利运行,为之后的Http请求处理主线提供了必要的框架运行环境。

我们在这里所说的运行环境和Struts2自身的运行环境不同,它是指建立在Web服务器之上,框架自身运行所必须的内置对象的集合。为了更好地对这些内置对象进行管理,Struts2引入了框架级别“容器”的概念。因而Struts2的初始化主线,实际上最终转化为对这个“容器”的初始化过程。有关这个“容器”的定义和初始化过程的细节,我们将在第五章中为读者解开谜团。

3.3.1.3 Struts2的Http请求处理主线

Struts2的Http请求处理主线是Struts2的核心主线,包含了Struts2处理Http请求、进行必要的数据处理和处理数据返回的全部过程。这条主线将在任何满足web.xml中所指定的URL Pattern的Http请求发生时进行响应,由doFilter方法负责驱动执行。

如果我们回顾一下Struts2核心入口程序的流程图(图2-4),我们可以看到Struts2的Http请求处理主线又被一条分割线划分成了两个不同的执行阶段:

第一阶段 ―― Http请求预处理

在这个阶段中,程序执行的控制权在Struts2手上。这个阶段的主要工作是针对每个Http请求进行预处理,为真正的业务逻辑执行做必要的数据环境和运行环境的准备。
程序代码在这个阶段有一个非常显著的特点:依赖于Web容器,并时时刻刻将与Web容器打交道作为主要工作。


第二阶段 ―― XWork执行业务逻辑

在这个阶段,程序执行的控制权被移交给了XWork。Struts2在完成了Http请求的预处理之后,将Http请求中的数据封装成为普通的Java对象,并由XWork负责执行具体的业务逻辑。
程序代码在这个阶段的特点和第一阶段完全相反:不依赖于Web容器,完全由XWork框架驱动整个执行的过程。

从Struts2对于Http请求的处理过程中,我们可以看出Struts2的核心设计理念在于解耦。所谓解耦,实际上是尽可能地消除核心程序对外部运行环境的依赖,从而保证核心程序能够更加专注于应用程序的逻辑本身。在Struts2中,我们所说的外部运行环境就是Web容器。我们在这里可以看到,Struts2的核心设计理念与Struts2的运行环境居然是一个矛盾体!

downpour 写道
结论 Struts2的核心设计理念在于消除核心程序对运行环境(Web容器)的依赖,而这一过程也是Struts2的解耦过程。


这种设计实现与目的之间的矛盾,却是Struts2的设计始终围绕着Web开发中的最佳实践的最有力证明,也是Struts2从设计理念开始,就优于其他表示层框架的精要所在。在解耦方面,Struts2也确实做到了2个不同的层面,从而使整个设计更加突显出其优秀之处:

从代码上进行物理解耦

Struts2将第一阶段中的代码整合到struts2-core-2.2.1.jar,而将第二阶段中的代码整合到xwork-core-2.2.1.jar。

将逻辑分配到不同的执行阶段

Struts2将处理数据的逻辑和处理业务的逻辑分配到2个不同的执行阶段,使得我们对于代码逻辑的关注点更为清晰。

因此,正如我们在之前的章节所谈到的,严格意义上的Struts2,实际上由2个不同的框架所组成。一个是真正意义上的Struts2,另外一个是XWork。从职责上来说,XWork才是真正实现MVC的框架,Struts2的工作是在对Http请求进行一定处理后,委托XWork完成真正的逻辑处理。将Web容器与MVC实现分离,是Struts2区别于其他Web框架的最重要的特性,也是最值得我们品味的一个宏观设计思路。当读者真正理解了其中的奥秘,相信读者也就会真正掌握Web开发之道了。

3.3.2显微镜 ―― Struts2的微观元素

在了解了Struts2的宏观面之后,我们再来一起探究一下构成这些宏观面的微观元素。同样的,不同的主线和不同的阶段所承担的职责不同,构成它们的微观元素也不尽相同。在本节中,我们将列出每条主线和每个执行阶段的主要组成元素。或许在这其中大家会接触到许多新名词,读者不妨首先感性地认识一下这些概念性的名词,可以不求甚解,因为我们会在后续的章节中对Struts2的这些元素一一展开分析和讲解。

3.3.2.1 第一条主线 ―― Struts2的初始化

在对Struts2初始化主线的宏观分析中,我们曾经谈到为了帮助更好地管理Struts2中的内置对象,Struts2引入了一个“容器”的概念,将所有需要被管理的对象全部置于容器之中。因而,整个Struts2初始化过程,也始终围绕着这个“容器”展开。除了“容器”,Struts2中的另一类配置元素PackageConfig,也是Struts2初始化的主要内容之一。如果我们从“数据 + 行为”的角度来分析,那么构成Struts2整个初始化过程的主要元素,就可以被分为数据结构的定义和初始化行为的操作接口两个部分。

从数据结构定义的角度,“容器”顺理成章地成为Struts2初始化主线中的核心构成元素,而PackageConfig作为事件请求映射的配置元素也成为了我们所需要重点关注的构成元素。它们的接口定义和实现类如表3-1所示:



表3-1的定义,从数据结构的角度指出了Struts2初始化流程的主要对象是哪些。而整个初始化的操作过程,则由另外两个相辅相成的元素配合共同完成,它们分别是加载接口(Provider)和构造器(Builder),其相关元素如表3-2所示:



Struts2初始化主线中还有一些辅助的元素,它们主要被用于承载这些配置加载接口并在初始化时驱动整个初始化流程的顺利执行。相关元素如表3-3所示:



Struts2的初始化是一个非常复杂的流程。在这里我们仅仅给出了部分主要的数据结构和基础操作接口。读者可以使用IDE的源码查看功能找到这些接口的众多实现类。从这些实现类中,大家会发现这些实现类实际上是对整个Struts2配置元素的一个总串联,之后的章节我们会通过源码分析它们的联系和运行机理。

3.3.2.2第二条主线 ―― 第一阶段 ―― Http请求预处理

在这个阶段,我们知道程序的执行控制权还控制在Struts2手中。所以在这个阶段中所涉及到的主要微观元素,都是Struts2的类。这些类的主要职责是与Web容器打交道。因而,从设计原则上,为了保持解耦,这个阶段做了大量的对象创建和对象转化的工作。当这些工作完成之后,就能交付第二个阶段继续执行。这个阶段的主要微观元素如表3-4所示:



虽然这个阶段所涉及到的元素很少,但是其中的Dispatcher却是整个Struts2框架的核心。Dispatcher被称之为核心分发器,是Struts2进行Http请求处理的实际场所。在整个处理流程中,Dispatcher不仅是Http请求预处理的实际执行者,更是将Http请求与Web容器进行解耦并进行逻辑处理转发的执行驱动核心。事实上,我们对Struts2框架内部机理的研究,都将以Dispatcher作为重要的切入点而展开。

3.3.2.2第二条主线 ―― 第二阶段 ―― XWork执行业务逻辑

在这个阶段,程序的执行控制权被移交到了XWork框架中。我们可以想象位于这个执行阶段的微观元素区别于第一个阶段的显著特点:它们都是XWork框架所定义的类。其相关微观元素的定义,如表3-5所示:



在这里,我们形象地把XWork比喻成了一条生产流水线。在这其中的每个元素,就像是生产线中的重要组成部分。XWork框架就像一个完整的事件执行器,进入框架中的事件就如同进入生产线中的原材料,会按照生产线中的定义依次执行并产生结果。
表3-5中的七个元素,被称之为XWork的七大元素,贯穿了XWork事件执行器的整个生命周期。它们各司其职,精心配合,提供了事件执行框架足够的扩展接口。不仅如此,它们之间的调用关系,也成为了XWork框架设计中的经典。这些元素的调用关系,如下图3-5所示:



这幅图不仅包含了XWork框架中各个元素的调用关系,还把整个XWork框架置于Web容器这样一个大的环境之中,同时给出了XWork框架的调用核心Dispatcher。我们在这里不再对这幅图做深入的剖析,读者可以大致了解XWork中这些元素的结构和层次关系。在第八章中,我们将从数据流和控制流这两个不同的角度,对这张图的程序流转方向进行详细的分析。
1 楼 ddkk 2011-10-28  
只会struts1.x,一直没深入2
看见文章中提到的 StrutsPrepareAndExecuteFilter
才搜到了
struts2 FilterDispatcher 和 StrutsPrepareAndExecuteFilter 的区别

2 楼 ddkk 2011-10-28  
FilterDispatcher是struts2.0.x到2.1.2版本的核心过滤器
StrutsPrepareAndExecuteFilter自2.1.3替代FilterDispatcher
这样改革好处:
如果想自定义过滤器, 要放在strtus2过滤器之前, 否则,自己的过滤器对action的过滤作用就废了,不会有效!除非是访问jsp/html
使用Action的环境,又想在执行action之前拿filter做一些事, FilterDispatcher做不到

StrutsPrepareAndExecuteFilter可以拆分成StrutsPrepareFilter和StrutsExecuteFilter,可以在这两个过滤器之间加上自己的过滤器

大概能干啥我知道了,LZ能否举一个比较恰当的例子来说明

3 楼 ddkk 2011-10-28  
3.3.1.3 最后
严格意义上的Struts2,实际上由2个不同的框架所组成。一个是真正意义上的Struts2,另外一个是XWork。从职责上来说,XWork才是真正实现MVC的框架,Struts2的工作是在对Http请求进行一定处理后,委托XWork完成真正的逻辑处理。将Web容器与MVC实现分离,是Struts2区别于其他Web框架的最重要的特性,也是最值得我们品味的一个宏观设计思路

大亮
4 楼 downpour 2011-10-28  
ddkk 写道
FilterDispatcher是struts2.0.x到2.1.2版本的核心过滤器
StrutsPrepareAndExecuteFilter自2.1.3替代FilterDispatcher
这样改革好处:
如果想自定义过滤器, 要放在strtus2过滤器之前, 否则,自己的过滤器对action的过滤作用就废了,不会有效!除非是访问jsp/html
使用Action的环境,又想在执行action之前拿filter做一些事, FilterDispatcher做不到

StrutsPrepareAndExecuteFilter可以拆分成StrutsPrepareFilter和StrutsExecuteFilter,可以在这两个过滤器之间加上自己的过滤器

大概能干啥我知道了,LZ能否举一个比较恰当的例子来说明



在我的书里有,呵呵。
5 楼 ddkk 2011-10-28  
最后,求一下 作者能在结合源码分析的同时,能再写一下结合struts 2.x实际开发时的配置实践
顺便问问作者还在上海不,求上门取签名版的书

6 楼 downpour 2011-10-28  
ddkk 写道
最后,求一下 作者能在结合源码分析的同时,能再写一下结合struts 2.x实际开发时的配置实践
顺便问问作者还在上海不,求上门取签名版的书


有关配置实践我本来想写,但是由于问题比较尖锐,会涉及到框架之争。所以我就没有在我的书里面包含这部分内容。

后续我会写一篇Struts2和SpringMVC的比较文章,其中会有这部分内容。

我的书不做签名,谢谢!
7 楼 ddkk 2011-10-28  
downpour 写道
ddkk 写道
最后,求一下 作者能在结合源码分析的同时,能再写一下结合struts 2.x实际开发时的配置实践
顺便问问作者还在上海不,求上门取签名版的书


有关配置实践我本来想写,但是由于问题比较尖锐,会涉及到框架之争。所以我就没有在我的书里面包含这部分内容。

后续我会写一篇Struts2和SpringMVC的比较文章,其中会有这部分内容。

我的书不做签名,谢谢!


比较的文章。等啊。。。
那有作者盖章版不
8 楼 jyjava 2011-12-30  
不错,好文章
9 楼 jyjava 2012-01-13  
第二遍看(三)了,这篇讲得有实质性的东西,呵呵
10 楼 chen5287603 2012-05-30  
第二遍看了。感谢作者
11 楼 zyh_1986 2012-08-21  
正在看源码,有时看地很迷糊,就上来找源码分析的文章,看到本文,不过瘾啊!
12 楼 codeboy_wang 2012-09-20  
13 楼 xiaozhang0731 2012-10-18  
收藏!收藏!收藏!
  相关解决方案