《Struts2技术内幕》 新书部分篇章连载(十)—— XWork概览
程序员文章站
2022-03-02 21:05:37
...
第7章 别具匠心 —— XWork设计原理
7.3 XWork概览
在了解了数据流和控制流的来龙去脉之后,我们再来看看XWork中实现这两大核心驱动力的编程元素以及它们之间的调用关系。相信有了之前所有的概念做铺垫,无论是XWork的宏观视图还是微观视图,读者理解起来应该可以驾轻就熟。
7.3.1 XWork的宏观视图
XWork的宏观构成示意图是XWork体系结构的核心,这个示意图我们曾经在第三章中向读者展示过,不过当时我们的引入此图的主要目的是为了说明XWork框架是Struts2的一个重要的组成部分,其具有非常丰富的内容。因而当时我们也并没有对其中的构成要素进行深入的分析。经过了之后各个章节的讲述,或许读者在这里会对这个示意图有一番更深刻的见解。整个示意图如图7-9所示:
事实上,这张XWork的宏观示意图是整个XWork乃至整个Struts2的核心。此图内涵丰富,几乎涵盖了XWork的元素构成、XWork中元素的调用关系、XWork的执行层次以及XWork与外部调用接口之间的关系等所有XWork框架的核心内容。
在这里,我们将首先帮助读者理解这张示意图中的一些基本概念和基本的逻辑层次,在下一章中,我们将对示意图中的每一个元素进行详细的分析。
大家对于这张示意图的第一印象一定是这张图中不同类型的框框(有虚线的,也有实线的)。而这些不同的框框所围起来的构成要素,实际上代表着XWork框架中三个不同的体系结构:
1. 核心分发器Dispatcher
核心分发器Dispatcher位于整个示意图的最外层的Web容器中,它本身不属于XWork框架的组成部分。我们在这里把它纳入到XWork宏观示意图中,并作为一个重要的组成体系的主要原因在于它在Struts2中有着非常重要的低位。被称之为核心分发器的Dispatcher运行于Web容器中,却成为了XWork框架的调用者和驱动执行者。在图中,我们可以看到在核心分发器Dispatcher和下面的XWork元素之间有一条明显的虚线分割线作为它们之间的区分标志。
2. XWork的控制流体系
XWork的控制流体系是指XWork进行请求响应的一系列执行元素,这一系列执行元素包括:ActionProxy、ActionInvocation、Interceptor、Action和Result。这些元素位于图中的下半部分,并被一个实线框封装于内部,由ActionProxy驱动执行。它们之所以被称之为XWork的控制流体系,是因为这些元素是真正的请求响应的逻辑处理载体,控制着整个请求响应的执行过程。
3. XWork的数据流体系
XWork的数据流体系是只XWork在进行请求响应时所依赖的一个数据环境,而这个数据环境中包含了两大元素:ActionContext和ValueStack。这两大元素在图中所在的位置也比较特殊,它们都被虚线框包含其中。ActionContext所在的虚线框同时囊括了XWork控制流体系中的所有元素;而ValueStack所在的虚线框则囊括了核心控制元素的Action。这表明XWork的数据流元素在不同的控制流执行元素之间形成了有层次的共享,它虽然不直接参与控制流的执行体系,却是控制流执行过程中必不可少的核心依赖。
如果单单把重心放在XWork框架本身,我们可以发现XWork的体系结构正是按照控制流和数据流这两大核心驱动力进行划分的。结合核心分发器Dispatcher,我们可以归纳出这三大体系结构之间的层次关系:
再来谈谈“映衬关系”。映衬关系则是一种非常微妙的关系。所谓“映衬”,实际上体现了一种“你中有我,我中有你”的水乳-交融的联系。XWork框架的控制流体系的执行基础是其数据流中的元素;而另外一个方面,失去了控制流的执行流程,数据流的所有元素也没有存在的意义了。
我们可以看到,“解耦合”这样一个开发中的最佳实践被XWork充分挖掘。XWork将数据流和控制流这两大驱动程序运行的基本力量进行物理隔离,将它们分派到不同的执行元素之上。但在运行期,两者又通过某种编程手段有机联系在了一起。这种看似很松,实际很紧的联系正是贯穿XWork框架总体设计的一个核心思想。
7.3.2 XWork的微观视图
当我们拥有了图7-9,实际上所有的XWork微观构成元素也通过示意图完全呈现在了读者的面前。对于所有这些微观元素的解读,也自然离不开对图7-9中元素和元素之间关系的解读。因而在这里,我们还是从XWork的数据流和控制流这两个截然不同的体系上,对XWork的微观构成给出一个大致的介绍。在下一章中,我们不仅将细化这些微观构成元素,还将具体展开这些微观元素之间的关系。
7.3.2.1数据流元素
XWork的数据流体系,在图7-9中以虚线框的形式存在。我们可以在虚线框中看到构成数据流的两大元素:ActionContext和ValueStack:
1. ActionContext —— 数据环境
ActionContext是一个独立的数据结构,其主要作用是为XWork的执行提供数据环境。无论是请求的参数,还是处理的返回值,甚至一些原生的Web容器对象,都被封装于ActionContext的内部,成为了Struts2 / XWork执行时所依赖的数据基础。
2. ValueStack —— 数据访问环境
ValueStack本身是一个数据结构,其主要作用是用以对OGNL计算进行扩展。因而,位于ActionContext之中的ValueStack则赋予了ActionContext进行数据计算的功能,从而使得ValueStack自身成为了一个可以进行数据访问的环境。
在XWork对数据流的设计中,首要的考虑因素是功能性。根据之前我们对数据流的分析,构成数据流的元素,有两大基础的功能性要求:数据存储和数据传输。如果我们仔细分析ActionContext和ValueStack这两大元素,我们会发现ActionContext刚好是一个数据存储的容器,而ValueStack则接过了数据传输的责任大旗。这两大元素的相互配合,很好地诠释了数据流的完整过程。
那么,XWork对于数据流的设计有什么独到之处呢?
结合图7-6,我们可以看到数据流的主要构成:请求内容和响应内容。如果回顾一下传统的参数-返回值(Param-Return)模式和参数-参数(Param-Param)模式,我们会发现无论是请求内容还是响应内容,它们在表现形式上都作为方法(Method)的一个重要组成部分(要么作为方法的参数、要么就作为方法的返回值)。而作为控制流主要载体的方法(Method)本身,对数据流元素形成了语法依赖。也就是说,在这种情况下,数据流和控制流之间是天然耦合的。因为作为控制流实现的主体方法,它与参数和返回值在语法层面被紧密联系在了一起。
而XWork采用了与控制流完全独立的对象实体来封装所有的数据流元素,并将控制流中的核心处理要素(Action)置于数据流之中,使两者形成水乳-交融的关系。这种设计的理念基于“解耦合”这样一个思想,使得原本无法分离的编程元素成为了形式上独立,运行上又紧密联系在一起的组件。这一点,正是XWork在数据流设计上的精华之处,也是读者需要细细品味的地方。
7.3.2.2控制流元素
XWork的控制流体系,在图7-9中以实线框的形式存在。我们在实线框中可以看到构成控制流的元素主要有五个:ActionProxy、ActionInvocation、Interceptor、Action和Result。
这五大元素从功能逻辑上进行划分,还可以分成两类:其中的Interceptor、Action和Result被用于定义事件处理的基本流程,我们称之为事件处理节点;ActionProxy和ActionInvocation在事件处理的过程中起到的作用主要是对事件处理节点进行调度执行,我们将其称之为事件处理驱动元素。
我们在7.2.3章节中曾经深入分析过控制流的内部实现细节,并归纳了控制流的四大职责。不过当时我们并没有点出其中蕴含的一个XWork设计中的潜台词:
正是基于这样一个基本观点,XWork建立起了一套定义事件处理流程的方法,并将它们映射到具体的Java对象中去。
1. Action —— 核心处理类
Action是XWork所定义的一个核心的事件处理接口。这个接口定义仅仅定义了一个没有参数的响应方法,从而使得所有实现Action接口的事件处理类,都自然而然地工作在属性-行为模式之上。其中,响应方法的内部完成对核心业务的处理,而事件处理类的内部属性则成为了响应方法进行业务处理的数据来源和响应结果。
2. Interceptor —— 拦截器
Interceptor本身是AOP的概念,表示对程序的某个逻辑执行点进行拦截,从而能够在这个逻辑执行点之前、之后或者环绕着这个逻辑执行点进行逻辑扩展。在XWork中,Interceptor的拦截对象是核心处理类Action,从而在Action的周围定义了一个环绕的逻辑扩展层次,其主要作用就在于能够在核心处理类Action的执行之前、之后进行自定义的逻辑行为扩展。
Result —— 执行结果
Result是XWork定义用以对核心处理类Action执行完毕之后的响应处理动作。Result被定义为一个独立的逻辑执行层次,其主要作用是使核心处理类Action能够更加关注其核心业务流程的处理,而将程序的跳转控制逻辑交给Result来完成。
通过Action、Interceptor和Result这三大元素的相互配合,一个完整的事件处理流程就可以被定义为:以Action为业务处理核心,Interceptor进行逻辑辅助,Result进行响应逻辑跳转的具有丰富的执行层次的事件处理体系。
如果回顾一下在7.2.3这一小节有关对控制流细节的描述,我们会发现三大元素的完整定义实际上完成了我们对事件处理流程进行规范化流程中的前两个步骤:划分事件处理流程步骤和定义事件处理节点对象。而整个规范化流程中最为关键的步骤,也就是组织事件处理节点对象的执行次序,XWork则通过另外两个辅助对象来完成:
1. ActionProxy —— 执行环境
ActionProxy是整个XWork框架的执行入口。ActionProxy的存在,相当于定义了一个事件处理流程的执行范围,规定在ActionProxy内部的一切都属于XWork框架的管辖范围,而在ActionProxy之外,则只能以调用者的身份,与整个XWork的事件执行体系进行通讯。因此,ActionProxy的主要作用就在于对外屏蔽整个控制流核心元素的执行过程,对内则为Action、Interceptor、Result等事件处理节点对象提供了一个无干扰的执行环境。
2. ActionInvocation —— 核心调度器
ActionInvocation是组织起Action、Interceptor、Result等事件处理节点对象执行次序的核心调度器。ActionInvocation被封装于ActionProxy的内部,成为了XWork内部真正事件处理流程的总司令,它的执行调度过程,实际上控制着整个XWork事件处理体系的执行命脉。
在XWork的微观构成中,我们可以发现XWork的设计理念始终是将逻辑职责分派到最合适的编程元素之上。或许在这里我们还无法具体体会出XWork对这些具体元素的划分依据,不过我们已经可以从这些元素的名称中看出它们在框架中所处的不同地位。在下一章中,我们将对这些元素进行详细解读。
【XWork控制流元素的一个形象比喻】
XWork控制流被划分为五大元素:Action、Interceptor、Result、ActionProxy、ActionInvocation。我们可以使用一个战斗序列,来对这五大元素之间的关系进行诠释。
每当一个战役打响的时候,总指挥部总是需要分派一个具体*的部队(ActionProxy)来执行战斗。任何一支部队,都有主力军(Action)和策应部队(Interceptor)。主力军(Action)负责核心战斗,而策应部队(Interceptor)则负责对主力部队进行策应和援助。然而,所有的战斗指令都是由部队的指挥官(ActionInvocation)决定的。指挥官(ActionInvocation)是一个部队(ActionProxy)的核心,他将负责主力部队(Action)和策应部队(Interceptor)的调度。当一个战斗结束以后,指挥官(ActionInvocation)还要负责指挥部队下一步的动向(Result),是乘胜追击敌人,还是继续待命。
7.3 XWork概览
在了解了数据流和控制流的来龙去脉之后,我们再来看看XWork中实现这两大核心驱动力的编程元素以及它们之间的调用关系。相信有了之前所有的概念做铺垫,无论是XWork的宏观视图还是微观视图,读者理解起来应该可以驾轻就熟。
7.3.1 XWork的宏观视图
XWork的宏观构成示意图是XWork体系结构的核心,这个示意图我们曾经在第三章中向读者展示过,不过当时我们的引入此图的主要目的是为了说明XWork框架是Struts2的一个重要的组成部分,其具有非常丰富的内容。因而当时我们也并没有对其中的构成要素进行深入的分析。经过了之后各个章节的讲述,或许读者在这里会对这个示意图有一番更深刻的见解。整个示意图如图7-9所示:
事实上,这张XWork的宏观示意图是整个XWork乃至整个Struts2的核心。此图内涵丰富,几乎涵盖了XWork的元素构成、XWork中元素的调用关系、XWork的执行层次以及XWork与外部调用接口之间的关系等所有XWork框架的核心内容。
在这里,我们将首先帮助读者理解这张示意图中的一些基本概念和基本的逻辑层次,在下一章中,我们将对示意图中的每一个元素进行详细的分析。
大家对于这张示意图的第一印象一定是这张图中不同类型的框框(有虚线的,也有实线的)。而这些不同的框框所围起来的构成要素,实际上代表着XWork框架中三个不同的体系结构:
1. 核心分发器Dispatcher
核心分发器Dispatcher位于整个示意图的最外层的Web容器中,它本身不属于XWork框架的组成部分。我们在这里把它纳入到XWork宏观示意图中,并作为一个重要的组成体系的主要原因在于它在Struts2中有着非常重要的低位。被称之为核心分发器的Dispatcher运行于Web容器中,却成为了XWork框架的调用者和驱动执行者。在图中,我们可以看到在核心分发器Dispatcher和下面的XWork元素之间有一条明显的虚线分割线作为它们之间的区分标志。
2. XWork的控制流体系
XWork的控制流体系是指XWork进行请求响应的一系列执行元素,这一系列执行元素包括:ActionProxy、ActionInvocation、Interceptor、Action和Result。这些元素位于图中的下半部分,并被一个实线框封装于内部,由ActionProxy驱动执行。它们之所以被称之为XWork的控制流体系,是因为这些元素是真正的请求响应的逻辑处理载体,控制着整个请求响应的执行过程。
3. XWork的数据流体系
XWork的数据流体系是只XWork在进行请求响应时所依赖的一个数据环境,而这个数据环境中包含了两大元素:ActionContext和ValueStack。这两大元素在图中所在的位置也比较特殊,它们都被虚线框包含其中。ActionContext所在的虚线框同时囊括了XWork控制流体系中的所有元素;而ValueStack所在的虚线框则囊括了核心控制元素的Action。这表明XWork的数据流元素在不同的控制流执行元素之间形成了有层次的共享,它虽然不直接参与控制流的执行体系,却是控制流执行过程中必不可少的核心依赖。
如果单单把重心放在XWork框架本身,我们可以发现XWork的体系结构正是按照控制流和数据流这两大核心驱动力进行划分的。结合核心分发器Dispatcher,我们可以归纳出这三大体系结构之间的层次关系:
- 调用关系
- 映衬关系
再来谈谈“映衬关系”。映衬关系则是一种非常微妙的关系。所谓“映衬”,实际上体现了一种“你中有我,我中有你”的水乳-交融的联系。XWork框架的控制流体系的执行基础是其数据流中的元素;而另外一个方面,失去了控制流的执行流程,数据流的所有元素也没有存在的意义了。
我们可以看到,“解耦合”这样一个开发中的最佳实践被XWork充分挖掘。XWork将数据流和控制流这两大驱动程序运行的基本力量进行物理隔离,将它们分派到不同的执行元素之上。但在运行期,两者又通过某种编程手段有机联系在了一起。这种看似很松,实际很紧的联系正是贯穿XWork框架总体设计的一个核心思想。
7.3.2 XWork的微观视图
当我们拥有了图7-9,实际上所有的XWork微观构成元素也通过示意图完全呈现在了读者的面前。对于所有这些微观元素的解读,也自然离不开对图7-9中元素和元素之间关系的解读。因而在这里,我们还是从XWork的数据流和控制流这两个截然不同的体系上,对XWork的微观构成给出一个大致的介绍。在下一章中,我们不仅将细化这些微观构成元素,还将具体展开这些微观元素之间的关系。
7.3.2.1数据流元素
XWork的数据流体系,在图7-9中以虚线框的形式存在。我们可以在虚线框中看到构成数据流的两大元素:ActionContext和ValueStack:
1. ActionContext —— 数据环境
ActionContext是一个独立的数据结构,其主要作用是为XWork的执行提供数据环境。无论是请求的参数,还是处理的返回值,甚至一些原生的Web容器对象,都被封装于ActionContext的内部,成为了Struts2 / XWork执行时所依赖的数据基础。
2. ValueStack —— 数据访问环境
ValueStack本身是一个数据结构,其主要作用是用以对OGNL计算进行扩展。因而,位于ActionContext之中的ValueStack则赋予了ActionContext进行数据计算的功能,从而使得ValueStack自身成为了一个可以进行数据访问的环境。
在XWork对数据流的设计中,首要的考虑因素是功能性。根据之前我们对数据流的分析,构成数据流的元素,有两大基础的功能性要求:数据存储和数据传输。如果我们仔细分析ActionContext和ValueStack这两大元素,我们会发现ActionContext刚好是一个数据存储的容器,而ValueStack则接过了数据传输的责任大旗。这两大元素的相互配合,很好地诠释了数据流的完整过程。
那么,XWork对于数据流的设计有什么独到之处呢?
downpour 写道
结论 XWork对于数据流设计的亮点,在于数据流元素被设计成独立的数据结构。
结合图7-6,我们可以看到数据流的主要构成:请求内容和响应内容。如果回顾一下传统的参数-返回值(Param-Return)模式和参数-参数(Param-Param)模式,我们会发现无论是请求内容还是响应内容,它们在表现形式上都作为方法(Method)的一个重要组成部分(要么作为方法的参数、要么就作为方法的返回值)。而作为控制流主要载体的方法(Method)本身,对数据流元素形成了语法依赖。也就是说,在这种情况下,数据流和控制流之间是天然耦合的。因为作为控制流实现的主体方法,它与参数和返回值在语法层面被紧密联系在了一起。
而XWork采用了与控制流完全独立的对象实体来封装所有的数据流元素,并将控制流中的核心处理要素(Action)置于数据流之中,使两者形成水乳-交融的关系。这种设计的理念基于“解耦合”这样一个思想,使得原本无法分离的编程元素成为了形式上独立,运行上又紧密联系在一起的组件。这一点,正是XWork在数据流设计上的精华之处,也是读者需要细细品味的地方。
7.3.2.2控制流元素
XWork的控制流体系,在图7-9中以实线框的形式存在。我们在实线框中可以看到构成控制流的元素主要有五个:ActionProxy、ActionInvocation、Interceptor、Action和Result。
这五大元素从功能逻辑上进行划分,还可以分成两类:其中的Interceptor、Action和Result被用于定义事件处理的基本流程,我们称之为事件处理节点;ActionProxy和ActionInvocation在事件处理的过程中起到的作用主要是对事件处理节点进行调度执行,我们将其称之为事件处理驱动元素。
我们在7.2.3章节中曾经深入分析过控制流的内部实现细节,并归纳了控制流的四大职责。不过当时我们并没有点出其中蕴含的一个XWork设计中的潜台词:
downpour 写道
结论 结论:XWork认为,一个事件处理的流程是有规律并可以被继续细化的。
正是基于这样一个基本观点,XWork建立起了一套定义事件处理流程的方法,并将它们映射到具体的Java对象中去。
1. Action —— 核心处理类
Action是XWork所定义的一个核心的事件处理接口。这个接口定义仅仅定义了一个没有参数的响应方法,从而使得所有实现Action接口的事件处理类,都自然而然地工作在属性-行为模式之上。其中,响应方法的内部完成对核心业务的处理,而事件处理类的内部属性则成为了响应方法进行业务处理的数据来源和响应结果。
2. Interceptor —— 拦截器
Interceptor本身是AOP的概念,表示对程序的某个逻辑执行点进行拦截,从而能够在这个逻辑执行点之前、之后或者环绕着这个逻辑执行点进行逻辑扩展。在XWork中,Interceptor的拦截对象是核心处理类Action,从而在Action的周围定义了一个环绕的逻辑扩展层次,其主要作用就在于能够在核心处理类Action的执行之前、之后进行自定义的逻辑行为扩展。
Result —— 执行结果
Result是XWork定义用以对核心处理类Action执行完毕之后的响应处理动作。Result被定义为一个独立的逻辑执行层次,其主要作用是使核心处理类Action能够更加关注其核心业务流程的处理,而将程序的跳转控制逻辑交给Result来完成。
通过Action、Interceptor和Result这三大元素的相互配合,一个完整的事件处理流程就可以被定义为:以Action为业务处理核心,Interceptor进行逻辑辅助,Result进行响应逻辑跳转的具有丰富的执行层次的事件处理体系。
如果回顾一下在7.2.3这一小节有关对控制流细节的描述,我们会发现三大元素的完整定义实际上完成了我们对事件处理流程进行规范化流程中的前两个步骤:划分事件处理流程步骤和定义事件处理节点对象。而整个规范化流程中最为关键的步骤,也就是组织事件处理节点对象的执行次序,XWork则通过另外两个辅助对象来完成:
1. ActionProxy —— 执行环境
ActionProxy是整个XWork框架的执行入口。ActionProxy的存在,相当于定义了一个事件处理流程的执行范围,规定在ActionProxy内部的一切都属于XWork框架的管辖范围,而在ActionProxy之外,则只能以调用者的身份,与整个XWork的事件执行体系进行通讯。因此,ActionProxy的主要作用就在于对外屏蔽整个控制流核心元素的执行过程,对内则为Action、Interceptor、Result等事件处理节点对象提供了一个无干扰的执行环境。
2. ActionInvocation —— 核心调度器
ActionInvocation是组织起Action、Interceptor、Result等事件处理节点对象执行次序的核心调度器。ActionInvocation被封装于ActionProxy的内部,成为了XWork内部真正事件处理流程的总司令,它的执行调度过程,实际上控制着整个XWork事件处理体系的执行命脉。
在XWork的微观构成中,我们可以发现XWork的设计理念始终是将逻辑职责分派到最合适的编程元素之上。或许在这里我们还无法具体体会出XWork对这些具体元素的划分依据,不过我们已经可以从这些元素的名称中看出它们在框架中所处的不同地位。在下一章中,我们将对这些元素进行详细解读。
【XWork控制流元素的一个形象比喻】
XWork控制流被划分为五大元素:Action、Interceptor、Result、ActionProxy、ActionInvocation。我们可以使用一个战斗序列,来对这五大元素之间的关系进行诠释。
每当一个战役打响的时候,总指挥部总是需要分派一个具体*的部队(ActionProxy)来执行战斗。任何一支部队,都有主力军(Action)和策应部队(Interceptor)。主力军(Action)负责核心战斗,而策应部队(Interceptor)则负责对主力部队进行策应和援助。然而,所有的战斗指令都是由部队的指挥官(ActionInvocation)决定的。指挥官(ActionInvocation)是一个部队(ActionProxy)的核心,他将负责主力部队(Action)和策应部队(Interceptor)的调度。当一个战斗结束以后,指挥官(ActionInvocation)还要负责指挥部队下一步的动向(Result),是乘胜追击敌人,还是继续待命。
推荐阅读
-
《Struts2技术内幕》 新书部分篇章连载(二)—— 面向对象浅谈
-
《Struts2技术内幕》 新书部分篇章连载(四)—— 核心分发器
-
《Struts2技术内幕》 新书部分篇章连载(三)—— 多视角透析Struts2
-
《Struts2技术内幕》 新书部分篇章连载(七)—— ThreadLocal模式
-
《Struts2技术内幕》 新书部分篇章连载(六)—— 框架的本质
-
《Struts2技术内幕》 新书部分篇章连载(五)—— 请求响应哲学
-
《Struts2技术内幕》 新书部分篇章连载(八)—— XWork容器概览
-
《Struts2技术内幕》 新书部分篇章连载(十)—— XWork概览
-
《Struts2技术内幕》 新书部分篇章连载(九)—— 强大的OGNL
-
《Struts2技术内幕》 新书部分篇章连载(一)—— 如何学习开源框架