第7章 别具匠心 ―― XWork设计原理
众所周知,现代电子计算机由5大部件组成:运算器、控制器、存储器、输入设备和输出设备。其中,运算器和控制器合称CPU,是计算机中最为核心的部分。如果我们把整个Struts2框架比作是一个计算机,那么XWork则是Struts2框架中的CPU,是Struts2运行机制的核心。
那么,XWork到底是一个什么样的开发框架呢?我们曾经在本书的第三章中对XWork框架有一个初步的介绍:
引用
XWork is a command-pattern framework that is used to power Struts 2 as well as other applications. XWork provides an Inversion of Control container, a powerful expression language, data type conversion, validation, and pluggable configuration.
按照XWork框架官方的定义,XWork是为了建立一套灵活而可靠的基于命令模式的开发框架。所谓“命令模式”,其实本质上是一个请求-响应模式。因而,基于命令模式的开发框架,也就是指一个开发框架实现人机交互、实现请求-响应模式的过程。
在本章中,我们就从人机交互的基本模式(请求-响应模式)开始讲起,通过对请求-响应模式的实现机理的分析,引出XWork框架在实现请求-响应模式过程中的独特的设计理念,并对XWork框架的体系结构进行系统地阐述。
7.1 请求-响应的哲学
7.1.1请求-响应的基本概念
人和人之间的沟通与交流,在绝大多数情况下是通过语言沟通来完成的。语言,就好比一个双方都能够认同的协议,因为只有交流的双方都能够听懂某一种语言,双方之间的沟通才有了起码的基础,交流才能有效地进行。你一言我一语的交流可以被看作是一个回合制的请求-响应过程,因为每一个沟通的回合实际上被看作是一个发起方进行请求、响应方进行响应的交互过程。我们可以用一个示意图来表达整个交互的过程,这个过程看起来大概就像是如图7-1所示的样子:
在图中,我们可以看到一个完整的沟通流程,主要有四个构成要素:
- 沟通协议 ―― 某种双方都能明白的沟通机制,例如语言、手势等。
- 发起方 ―― 沟通的发起者。
- 沟通内容 ―― 交流的具体内容,例如,“你吃了嘛?”。
- 响应方 ―― 沟通的接收方和响应者
这种沟通方式在人类日常生活中是最为常见的一种模式。在上述的这四个构成要素中,我们又可以小结出两个主要特点:
沟通协议是沟通内容的基础,沟通内容是沟通协议的具体表现形式
发起方和响应方的角色并不固定,只有在一个交互回合中才能确定角色
如果我们把沟通延伸到人与机器之间,上述的模式就会发生一些变化。因为至少有一点值得我们注意,从人机交互的特点上讲,机器是不会主动发起沟通请求的。这样一来,人机交互的整个过程,就如图7-2所示:
从图中,我们可以看到人机交互的构成要素就变成了以下三个元素:
- 沟通协议 ――人和机器都能够明白的数据通讯格式
- 请求内容 ―― 人通过某种机制向机器发起的数据请求
- 响应内容 ―― 机器接收到数据请求并做逻辑处理之后,进行响应的数据内容
因为请求方和响应方是固定的,因而在人机交互的过程中,我们关注的重点内容就转变为:人作为沟通的发起方,其请求的内容是什么?机器作为命令的接收方和响应方,在其处理完毕之后,返回的内容是什么?在这种情况下,基于人机交互的请求-响应模式的概念也就应运而生了:
downpour 写道
结论 请求-响应模式是一种概念非常宽泛的人机交互模式,是人与计算机进行沟通的一种最基本的行为方式。
把视角拉回到我们的Web开发,我们就会发现基于B / S体系的Web应用是一个典型的基于请求-响应模式的体系架构。对于之前我们所谈到的请求-响应模式的三要素,我们也可以在其中找到对应关系:
- 沟通协议 ―― Http协议
- 请求内容 ―― Http请求
- 响应内容 ―― Http响应
浏览器发送Http请求到服务器端,服务器端的程序获得了Http请求后进行逻辑处理,并将逻辑处理的结果返回,这个返回的过程我们就称之为Http响应。请求和响应不断的交互过程,构成了所有B / S体系结构的应用构架的基础。如果我们把整个过程的通讯基础冠以一个官方的名称,它就是Http协议。
根据请求-响应模式总结出来的这三大要素自然而然也成为了我们在进行Web开发编程中的设计依据。在Servlet标准中,我们所熟悉的HttpServletRequest对象,就对应于这里的Http请求;而HttpServletResponse对象,则对应于Http响应。
由此可见,我们日常所说的进行Web开发的核心内容实际上就是指我们如何编写可运行于Web容器的服务器端程序用于进行Http请求的响应、进行逻辑处理并返回处理结果这样一个完整的过程。
7.1.2请求-响应的实现模式
我们来回顾一下基于Http协议的人机交互过程:由客户端浏览器发起一个Http请求,服务器端接收到请求之后进行逻辑处理,并将处理结果返回给客户端进行Http响应。我们所提出的问题是:这样一个过程,我们是如何使用Java语言来实现呢?
如果从Java的语法入手,通过分析对象的内部构成机理,我们可以得到三种风格迥异的请求-响应实现模式。这三种模式,在实际编程中也成为了不同的Web框架实现请求-响应模式的理论基础,接下来我们就来分别分析这三种不同的实现模式。
7.1.2.1参数-返回值(Param-Return)模式
参数-返回值(Param-Return)模式是一种最为直观的请求-响应的实现模式。我们曾经在第二章中详细分析过对象的一种运作模式:行为对象模式。这种行为对象模式,实际上就是我们这里所说的参数-返回值(Param-Return)模式的产生原型。
我们在第二章中讲到有关行为对象模式的概念时,我们当时给出了一个针对方法(Method)的构成分析。我们在这里继续引用当时的分析,并把它和请求-响应模式对应起来,整个过程如图7-3所示:
在这里,我们发现一个方法(Method)在语法上天然得能够与请求-响应模式相对应:
- 方法签名 ―― 请求-响应模式的处理载体
- 方法参数 ―― 请求内容映射
- 方法返回值 ―― 处理结果响应
因此,对象的方法(Method)定义与我们之前对请求-响应模式的流程是相通的,从而使得对象的方法(Method)成为了请求-响应模式在Java世界中的一种直观抽象。
在我们熟知的Web框架中,SpringMVC就是基于这种模式来定义和实现Http请求的处理和响应的。我们不妨来看一个典型的SpringMVC的Controller的示例,如代码清单7-1所示:
@Controller public class UserController { @RequestMapping("/login") public ModelAndView login(String userName, String password) { // 这里省略了login的业务逻辑处理 // 构建视图返回 ModelAndView modelAndView = new ModelAndView("index"); return modelAndView; } }
在上述代码中,方法的参数(userName和password)被视作是Http请求的概括,它们已经被SpringMVC的框架有效处理并屏蔽了内在的处理细节,呈现出来的是与请求参数名称一一对应的参数列表。而返回值ModelAndView则表示Http的响应是一个数据与视图的结合体,表示Http的处理结果。而函数体(login方法)本身,在其内部包含了进行逻辑处理的整个过程。
如果我们深入研究SpringMVC框架在Controller的定义中所支持的参数列表和返回值列表,我们可以发现SpringMVC所支持的内容非常宽泛,不仅在参数中支持与参数请求名称一一对应的参数列表,也支持HttpServletRequest等Web容器原生对象。事实上,万变不离其宗,无论参数列表如何变化,无论返回值的形式如何变化,参数-返回值(Param-Return)这样一种响应模式是其设计的核心内容。这种非常直观并符合简单逻辑思维的抽象方式,应该说也完全符合“简单是美(Simple is Beauty)”的最佳实践。
7.1.2.2参数-参数(Param-Param)模式
参数-参数(Param-Param)模式实际上比参数-返回值(Param-Return)模式更早出现。因为我们所熟知的Servlet标准就是基于参数-参数(Param-Param)模式进行设计的,因此这种参数-参数(Param-Param)模式也被称之为Servlet模式。
Servlet标准是J2EE的基本标准之一,我们在这里不妨来看看在Servlet标准中,定义了什么样的Http请求的处理接口。其相关接口定义,如图7-4所示:
在Servlet标准的接口定义中,我们可以看到service方法是进行Http请求处理的实际场所。因而就Http请求的响应本身而言,与我们之前所提到的参数-返回值(Param-Return)模式并没有什么本质区别。然而,service方法及其相关的doGet和doPost方法中,我们可以看到与之前参数-返回值(Param-Return)的不同之处:
- 参数列表 ―― Http请求被封装为一个HttpServletRequest对象(或者ServletRequest对象),而Http响应封装为一个HttpServletResponse对象(或者ServletResponse对象)
- 返回值 ―― 方法不存在返回值(返回值为void)
因此,这里最大的不同就在于将返回值的位置转移到了参数列表之中。这种将请求和响应同时置于参数位置的模式,就是我们所说的参数-参数(Param-Param)模式。
从上一节的分析中,我们可以看到参数-返回值(Param-Return)模式是对Java语言对于方法(Method)这种编程元素的解读。那么为什么Servlet作为Java规范中进行Http响应的标准,却要采用参数-参数(Param-Param)模式来实现呢?
Servlet作为一个开发标准,它所设计的接口已经无法再将任何处理职责继续往上层推送了,因为它是我们进行Web开发的底层标准。因而对于Http请求的处理,我们在Servlet中不仅需要知道返回给请求的发起者(浏览器)一个什么样的处理结果,还需要真实地将处理结果呈现在浏览器中。而这一点,我们不得不借助HttpServletResponse对象的操作在doGet和doPost方法体的内部调用相应的接口函数来完成。因而此时,方法的“返回值”对于Servlet对象来说没有任何操作上的意义,Servlet必须通过HttpServletResponse的调用操作来完成浏览器的响应工作,这也就是我们不得不把HttpServletResponse置于参数位置的原因了。
由此可见,参数-参数(Param-Param)模式是一种最为基础的请求-响应实现机制,也是底层规范不得不采用的一种实现机制。这种实现机制虽然原始(因为它需要我们自行处理Http的实现细节),然而它却是实现完整请求-响应机制的唯一途径。
我们在绝大多数的Web开发框架中,并不会看到Servlet模式这种原始的实现模式,因为几乎所有的Web框架都会以这种模式为基础,将具体实现转化成另外两种实现模式。不过这种参数-参数(Param-Param)模式在形式上为我们带来的逻辑意义,依旧是值得我们深入思考的重要方面。
7.1.2.3POJO模式
如果说参数-返回值(Param-Return)模式是请求-响应模式在Java世界中的一种直观抽象,那么POJO模式就是一种比较晦涩的请求-响应模式的实现方式了。接下来,我们来看看一个POJO模式的代码构成要素,如图7-5所示:
从图中,我们可以看出POJO模式与之前所介绍的两种实现模式之间的一个巨大不同就在于POJO模式完全颠覆了前两种实现模式中以Java类中的方法(Method)的语法特性作为原型基础进行请求响应的传统模式。在POJO模式中,我们看到虽然实际进行请求的处理和响应的载体依然还是POJO中的一个具体的方法(Method),但是我们却并没有在这个方法中看到任何参数。所有的请求参数,将以POJO内部属性变量的形式存在并被调用。不仅如此,所有的执行结果对象,也同样以POJO内部属性变量的形式存在。这样一来,POJO相对于某一次的响应是一个有状态响应。因为响应的处理流程、处理机制和处理结果,与当前POJO实例的内部属性的状态有关。
这就是POJO模式与前两种模式的最大区别:进行请求响应的处理类自身是否是一个有状态的对象。我们知道,传统的Servlet对象是一个无状态对象,也就是说它并不是一个线程安全的对象,我们不能在Servlet对象处理Http请求的过程中对Servlet对象内部的属性变量进行“安全”访问。这也就成为了Servlet对象处理Http请求无法逾越的一个障碍。从机制的角度讲,无论是参数-返回值(Param-Return)模式还是参数-参数(Param-Param)模式,它们都只是Servlet模式的有效变种。
而POJO模式则直接从概念上突破了Servlet对象的限制,将每一个请求的处理映射到一个线程安全的响应对象中去执行。因而从模式上讲,POJO模式是对传统的Servlet模式的一个重大改进,是一种崭新的请求-响应模式的实现。
7.1.3分歧和职责
7.1.3.1分歧何来?
无论哪一种对请求-响应的实现模式,实际上都是站在Java语言的语法角度,将请求-响应的过程进行编程元素的抽象化。很显然,三种不同的实现模式在实现上存在着一些分歧。它们之间的主要分歧在于:不同的实现模式使用了不同的编程元素(方法参数、方法返回值、类的属性)来表达请求-响应模式中不同的逻辑语义。而产生这些分歧的本质原因,我们可以从主观和客观两个不同的角度来分析:
主观上 ―― 不同的实现模式,它们考虑问题的出发点不同
这一点我们曾经在比较参数-参数(Param-Param)模式和参数-返回值(Param-Return)模式之间的区别时就提到过。前者作为一个底层的标准,不仅需要考虑到处理结果的返回(这可能是一个偏向于数据层面的响应结果),还需要考虑到后续的程序跳转(这就是偏向于控制层面的响应结果)。而后者,作为一个非底层的实现模式,则不需要考虑那么多的细节问题。
客观上 ―― 不同的编程元素(方法参数、方法返回值、类的属性)所能够表达的逻辑功能存在着天然的差异
这一点则在面向对象语言的语法层面表现得比较突出。很显然,既然一个编程语言设计了多种多样的编程元素,那么它本来的意思一定是期望不同的编程元素能够表达出不同的逻辑含义和功能。
我们以请求内容为例,我们可以看到请求内容主要有两种编程表现形式:方法参数和类的属性局部变量。很显然,作为方法参数更加符合“请求”这一动作的本意,这也完全符合编程语言的天然设计原理。而作为类的属性,却站在另外一个角度考虑问题。类的属性作为一个类的局部变量,从外部访问的角度我们可以为它添加setter和getter方法从而为这个类添加了JavaBean的特性。在这种情况下,我们可以很轻松地对类的属性变量进行访问。相反,我们要对位于方法中的参数列表进行访问,却是一件难上加难的事情,因为这些参数列表只有在运行期才能够决定。
因此,我们不难得出以下结论:
downpour 写道
结论 对于编程元素作用的理解不同,直接导致了不同请求-响应模式之间的差异。
我们可以发现,无论分歧发生在哪个具体的编程元素上,对于请求-响应这个过程本身而言都只是一个“表象”而非是“本质”。真正的本质,在于编程元素对于请求-响应过程不同元素职责的理解。
7.1.3.2职责何在?
虽然我们在这些实现模式上看到了分歧,不过既然它们都是对请求-响应模式的解读,它们之间一定有着共同的存在基础。我们可以发现,无论是哪种实现模式,都是在使用Java语言的语法元素来和整个请求-响应模式的过程中的元素进行配对。说得更加彻底一点,我们是在使用编程元素来对请求-响应的过程进行职责的划分。
那么,请求-响应模式的过程到底有哪些不可或缺的构成要素呢?我们还是使用一个示意图的形式来进行描述,如图7-6所示:
从图中,我们可以看到我们把整个请求-响应的过程划分为“请求”和“响应”这两个相反的过程,并分别继续根据其特点将整个过程划分为四个部分:
- 请求内容 ―― 请求的数据
- 请求处理载体 ―― 进行逻辑处理的场所
- 响应内容 ―― 逻辑处理的结果数据
- 响应跳转处理 ―― 逻辑处理完成后的程序流转方式
如果我们再把这上述的四个部分的内容和请求-响应的实现模式结合起来看,我们就能看到它们与Java语言中的编程元素的对应关系,如表7-1所示:
上述的分析,我们更多地站在了请求-响应模式本身的角度进行。因而在图7-6中,我们发现似乎请求和响应这两个过程被人为阻断了。但是如果我们站在程序开发的角度来看待问题,我们就可以把图7-6进一步进行扩展,如图7-7所示:
在图7-7中,我们发现我们使用了类似数学中的“合并同类项”的方式,把站在程序开发的角度功能相类似的模块用虚线框在了一起。从而形成了两个重要的程序开发概念:
- 数据流 ―― 描述程序运行过程中数据的流转方式及其行为状态
- 控制流 ―― 控制程序逻辑执行的先后顺序
在整个请求-响应的过程中,数据流实际上就表现为数据内容,其核心包括数据请求和数据响应;而控制流实际上表现为方法(Method)进行逻辑处理的过程,包含了程序的执行方向。我们发现,不仅是请求-响应模式,任何处于运行状态的程序,在程序内部总是有两股隐藏于内部的力量驱动着整个运行的过程:
downpour 写道
结论 数据流和控制流是两股隐藏于程序内部的神秘力量,是程序的运行的核心驱动力。
当我们在讨论请求-响应过程的职责划分时,不正是按照数据流和控制流的不同特性才对编程语言中的编程元素进行选择的吗?数据流和控制流,不也正是我们在程序开发的过程中所关注的两大职责嘛?
基于以上的结论,我们对XWork的研究,也将围绕着这两股驱动程序运行的核心力量。因而在下面的章节中,我们将首先讨论清楚数据流和控制流的方方面面,再结合XWork的体系结构阐述XWork对于数据流和控制流独具匠心的诠释。
1 楼
shine_sun
2011-10-28
讲述的很直观,很透彻!期待!
2 楼
ddkk
2011-10-28
表7-1 三者之间的分析,亮了
3 楼
KimHo
2011-10-28
这篇对我用处很大
4 楼
accphc
2011-10-31
一晚上浏览完了样章,最后一篇让我鸡冻了。期待佳作的出版。
5 楼
gigige
2011-10-31
看的我心痒痒~~很想买~~啥时上架呀??
6 楼
east_java
2011-12-19
引用
传统的Servlet对象是一个无状态对象,也就是说它并不是一个线程安全的对象,我们不能在Servlet对象处理Http请求的过程中对Servlet对象内部的属性变量进行“安全”访问
这句话是不是有些问题?
1、无状态对象,那么它应该是个线程安全对象。
2、为什么不能安全访问内部属性,因为在servlet类,添加了类属性,使Serlvet成为了有状态对象。所以多线程来访问时,变成了不安全。
7 楼
downpour
2011-12-19
east_java 写道
引用
传统的Servlet对象是一个无状态对象,也就是说它并不是一个线程安全的对象,我们不能在Servlet对象处理Http请求的过程中对Servlet对象内部的属性变量进行“安全”访问
这句话是不是有些问题?
1、无状态对象,那么它应该是个线程安全对象。
2、为什么不能安全访问内部属性,因为在servlet类,添加了类属性,使Serlvet成为了有状态对象。所以多线程来访问时,变成了不安全。
说法各有不同,看个人的理解。根据之前的定义,类的状态主要是由其内部属性决定的。所以Servlet对象在J2EE标准中被定义为一个无状态对象的意思是说,我们不能为其添加内部属性人为地把它变成一个有状态对象。而线程安全的概念首先就是建立在针对类的内部属性访问的基础上,所以我的这句话大问题应该没有,需要看个人的理解程度。
因为我在其他章节已经对这个问题说得比较透彻,这里就偷了个懒。
8 楼
chenchangqun11
2011-12-19
请问什么时候 这本书能买到 期待中
9 楼
jyjava
2011-12-30
能否将这个东西搞成pdf电子版的,下载下来看,呵呵
10 楼
jyjava
2012-01-13
lz,为啥纠结于一个对象是否是有无状态的,这篇文章,感觉很乱,就是两个请求-返回不同 模式,
11 楼
downpour
2012-01-13
jyjava 写道
lz,为啥纠结于一个对象是否是有无状态的,这篇文章,感觉很乱,就是两个请求-返回不同 模式,
因为这个纠结非常重要。只有搞清楚这个问题,才能搞清楚Struts2设计的根本问题。