用 continuation 开发复杂的 Web 应用程序(1)简介
- UID
- 1066743
|
用 continuation 开发复杂的 Web 应用程序(1)简介
由于 HTTP 天生的无状态性质,Web 技术遇到了这样一个问题:在两个连续的用户交互之间会遗忘状态信息。一个交互性的 Web 应用程序由一组脚本构成,每个交互都包含两个脚本,一个脚本向浏览器递交页面(然后结束),用户及时完成并提交表单,然后另一个(可能是不同的)脚本处理提交的表单。所以,应用程序逻辑分布在多个脚本之间。
由于浏览器还允许用户在交互中回溯,或者克隆一个正在处理中的交互过程,之后并行执行这两个交互过程,所以问题变得更加复杂。因为有这些可能,用户可以在任意时间在应用程序中能找到多条导航路径,所以您必须编写代码才能保证每个输出都成功。Web 开发框架(例如 Spring 和 Struts)允许您处理多条导航路径,但是它们做到这点的代价是:进一步提高了整体上已经很复杂的代码库(code base)的复杂性。
在本文中,我将介绍一个基于 continuation 的备选方案,该方案可以简化复杂 Web 应用程序的开发。我将从介绍 continuation 开始,讨论基于 continuation 的技术如何能够成为传统的 MVC 编程风格的有力武器。然后,我将转到一个简单的示例:一个企业应用程序,用它演示使用 continuation 在简化开发和使应用程序代码更容易理解方面的优势。因为使用 continuation 主要的不足之一是 Java 平台上缺少对它的支持,所以我采用 Apache Cocoon 框架来演示用 JavaScript 实现的示例程序,以及一个纯 Java 语言的实现。最后,我用对 continuation 优势与不足的进行概述,以此结束本文。
请选择本文顶部或底部的 Code图标下载示例应用程序的源代码。请参阅 ,下载 Apache Cocoon 框架,运行示例需要这个框架。
到底什么是 continuation ?传统上, continuation(继续)被定义为一个函数,它代表 "计算剩余的部分" 或者 "接下来要做的事"。换句话说,把中间结果(由前面的运算生成)发送给 continuation,会产生整体运算的最终结果。
例如,请看下面这个很基本的 Java 方法,它返回传给它的整数的平方:
清单 1. 计算整数输入平方值的方法1
2
3
4
| public static int computeSquare(int x)
{
return (x*x);
}
|
这个方法返回一个值,但是没有明确指定返回值的位置。而使用得当的 continuation 会明确指定返回的位置。
这样,假设我修改了以上方法(以及系统中的每个方法),在方法中包含了一个代表 continuation 的额外参数。通常,该参数应该是跟在方法中所有其他参数后面的最后一个参数。在调用函数时,它像以前那样执行内部逻辑,区别仅在于 返回的输出值,函数会把该值传递给 continuation,要求继续进行计算,从而利用输出的值 继续。这样,上面的方法就会重写,如清单 2 所示:
清单 2. 用 continuation 对象重写后的方法1
2
3
4
| public static int computeSquare(int x, Continuation c)
{
c.evaluate(x*x);
}
|
高阶函数请注意在清单 2 的示例中,函数 computeSquare实际上是一个高阶函数,因为它用函子(functor)对象( Continuation对象, c)作为参数,并用 x*x计算生成的中间结果向函子的 evaluate方法发送消息。
这种编程风格(不允许函数返回值)叫做 继续传递风格(Continuation Passing Style),或 CPS。函数 f1通过把应当返回的值传递给必须显式地传递给它的 continuation 函数,模拟了返回操作。同样,如果 f1在中间需要调用第二个函数 f2,那么它必须向 f2传递一个代表 " f1剩余部分" 的 continuation(与剩余的参数一起)。一旦 f2完成,那么代表 " f1剩余部分"的 continuation 会用 f2的计算结果继续进行计算。
现在,为了添加一点花样,我要引入另一个函数 f3,它在 f2的尾部被调用。如果 f2要照着 f1的方式做,传递它的 continuation,那么它最后只会把 f1的 continuation 传递给 f3。一旦已经执行了 f3,所要做的所有其余操作将是继续执行 f1的 continuation。
换种方式说,所谓 continuation就是保存下来的程序在指定时间点上的执行状态快照。有可能恢复这个状态,并从这一点起重新开始程序的执行,就像堆栈追踪,所有的本地变量以及程序的计数器都能重新找回自己原来的值。请参阅 参考资料,学习更多有关 continuation 的内容。现在我要把重点放在向您演示 continuation 在减少复杂 Web 应用程序上投入的编程精力方面能做些什么。在我们进入这个话题之前,请让我先花点时间进一步解释我要解决的问题。
常规 Web 开发中的问题模型 - 视图 - 控制器(Model-View-Controller,MVC)是广泛采用的交互式应用程序(包括 Web 程序)的开发模式。这个众所周知的模型把交互式应用程序组织成三个单独的模块:一个针对应用程序模型,代表数据和业务逻辑;第二个针对视图,提供数据表示和用户输入;第三个针对控制器,负责分派请求和控制流。
模型 1 架构 vs. 模型 2 架构严格地说,只有 MVC 的模型 2 架构有控制器 servlet。模型 1 架构的控制是分散的,在这种架构中浏览器直接与 servlet 或 JSP 技术交互,后二者则访问并使用表示应用程序模型的 JavaBean 组件。在这种架构中,要显示的下一页面由用户在当前页单击的链接或者随请求一起发送的参数决定。今天多数基于 MVC 的 Web 应用程序都实现了模型 2 架构。
那么控制器管理的这个 "流(flow)" 是什么呢?从页面加载、等候填写的表单返回角度来讲,典型的 Web 应用程序由定义良好的与用户进行交互的序列组成。在这种情况下,Web 应用程序就像是一个事件驱动的状态机(state machine)。这个事件模型就是典型的 MVC 架构通过控制器实现的模型。
例如,假设用户向服务器请求某个页面,页面中包含要填充的表单。用户花了些时间思考,填充答案,然后提交表单。当这个事件到达服务器(控制器模块)时,根据当前状态、用户提交的数据和业务逻辑,把应用程序移动到下一个逻辑状态。这种状态转换的结果,正如用户所看到的那样,是按顺序排列的或早期页面(和错误信息)的下一页的显示。
当状态机在从开始状态到结束状态的途中推进时,就重复这个循环,在某一点上,Web 应用程序被认为是实现了特定用例要求的功能。状态图控制着从 "开始" 状态到 "结束" 状态的各种可能的数据流,它既可以由控制器模块(通常是 servlet)显式实现的,也可以像在某些 Web 开发框架中那样,被外部化为配置文件中的元数据。
不论框架是如何实现的,与状态机的基本思想总是一致。在开发基于这个模型的 Web 应用程序时,就会出现大量问题,如下所述:
- 根据状态机的尺寸和维护客户当前状态所需要的数据量(因为一个 Web 应用程序在同一时间可能会有大量客户同时访问),应用程序逻辑可能会变得没有必要的杂乱或复杂。
- 在状态转换的序列中,客户单击浏览器的 Back 按钮的时间是不一定的,而且客户还可能克隆浏览器窗口,从而初始化并行的动作序列。任何一种情况都会导致已经传递的状态在原来的交互中发生多重(有时甚至是并发)提交。结果,应用程序必须跟踪每一个事务,并对每个事务提供正确的响应。
- 当 Web 应用程序要从跨多个页面的一系列表单中搜集用户信息时,也会出现类似的问题。如果后一个表单的生成取决于用户在前一个表单中提供的响应的组合,那么应用程序就必须跟踪在每个交互中输入的响应,并确保每一个交互都返回正确的页面。
一般来说,模型 2 Web 开发框架提供了定制技术,可以调节上述的一个或多个问题。但是,没有一个技术像基于 continuation 的方案那样直观、容易开发,基于 continuation 的方案提供了解决所有这些问题的一揽子方案。
关于事件驱动编程编程用户界面的事件驱动风格可以追溯到开始使用客户机 - 服务器架构的时候。它基于中央事件处理器以及在处理器上注册的大量事件句柄。每个句柄注册自己感兴趣的事件,当特定事件发生的时候,中央事件处理器会根据通知注册了事件的相关句柄。用户的交互状态在中央模块中维护,根据内部持有的当前状态,中央模块把引入的事件分派给注册的句柄。
大多数基于 Web 的互交是事件驱动编程的特例,在这里,界面显示被委托给 Web 浏览器,而不是由运行在用户工作站上的胖客户端可执行程序管理。虽然典型的胖客户端不允许用户驱动的功能(例如后向导航和克隆),但是 Web 浏览器支持甚至鼓励这类功能。当然,资源丰富的程序员已经找到了定制浏览器界面的方法(使用脚本代码),禁止这类操作,但是这样就形成了对不同浏览器的依赖性。
虽然 MVC 的事件驱动风格的编程有许多优势,但是它还造成业务功能分布到多个模块中,从而使它变得非常复杂,难以开发、理解、维护复杂程度合理的 Web 应用程序。虽然开发了许多 Web 开发框架(例如 Struts、Spring、以及 JavaServer Faces)来隐藏大多数 MVC 风格的界面背后的复杂结构,但是有一些开发人员已经开始认识到这样的事实:其他编程模型值得深入研究。 |
|
|
|
|
|