摘要
Spring Web Flow 是 _Spring Framework_ 中的 web 应用组件,它提供了一种编写有状态和基于会话的 web 应用的简便手段。Spring Web Flow 使得逻辑流程成为 Web 应用中的一等公民,它能让你定义为自包含模块,以独立于应用的其它部分来配置和重用。
Spring Web Flow 引入了几种有状态数据域:_request、flash、flow 和 conversation_ 等,这让你能用新的方式来开发有状态 Web 应用。它也提供了定制应用状态管理的扩展点。
_Terracotta for Spring_ 是通过在多个 JVM 集群来给基于 Spring 的应用提供高可用性的运行时。它给 _Spring Web Flows_ 的所有域都提供了透明的声明式集群服务(普通的 Spring beans 同样适用)。
在这篇文章中我们会首先给你一个 _Spring Web Flow_ 和 _Terracotta for Spring_ 的总体介绍。然后会向你展示如何联合使用这些技术来进入构建有状态、基于会话、可扩展和高可用的 Web 应用的新领域。
什么是 Spring Web Flow?
Spring Web Flow_ 是 _Spring Framework_ 中的 web 应用组件,它提供了一种编写有状态和基于会话的 web 应用的简便手段。Spring Web Flow 使得逻辑流程成为 web 应用中的一等公民,它能让你定义为自包含模块,以独立于应用的其它部分来配置和重用。它不依赖于框架从而能够方便的与可选的 web 应用框架一同使用,比如 _Spring MVC、Struts_ 或者 _JSF_ 等。_
页面流转使用一种领域定义语言(DSL)来配置,这个语言专门开发用来定义和组合页面流转。目前的实现方式是 XML 和 Java.
Spring Web Flow 引入了能满足多种用户案例和需求的几种有状态数据域:request、flash、flow 和 conversation,这给你开发有状态 web 应用提供了很大的灵活性和能力。
这里是 1.0 release 中最有趣特性的快速概要。(来自 release notes on InfoQ ):
- 在一个地方而不是把逻辑分散在很多地方来定义应用任务的所有控制逻辑,比如一个搜索流程。
- 把简单的流转组合在一起来创建富控制模块。
- 使用自然和面向对象的线性编程模型,而不是冗长的的 if/else 块来定义严格的用户导航规则。
- 但流转结束或过期时自动清除你在流转执行中分配的内存。
- 在使用你选择的基础 web 框架的 Servlet 环境中 Deploy 一个可执行的流转。
- 改变 web 框架(比如 Struts、Spring MVC、JSF 及其它)而不用修改流转定义。
- 和环境一起改变而不需要修改你的流转定义, 比如从 JUnit 测试到 Portlet。
- 开发时在不重启容器的情况下不断完善你的应用导航规则。
- 自动正确响应浏览器按钮(后退、前进、刷新)而不需要定制编程。
- 在 4 个受管理域中存储任务数据:request、flash、flow、和、conversation 等,每个都有自己的独特语义。
- 脱离容器单独测试流转。能在部署前确保应用控制逻辑能正常运作。
- 使用 Spring IDE 2.0 进行可视化编辑你的流转导航逻辑图
听上去很有趣?到目前为止还仅仅是概念和理论,但我们很快会看到这些都能在实践中应用。所以请多等一会。
企业对扩展性和高可用性的需求
集群在企业应用开发中变得越来越重要,开发人员经常会碰到这样的问题:
- 我们如何在一个节点上扩展来提高应用的可扩展性?
- 如果保证高可用性和消除单点故障,如何确保我们能满足客户的 SLAs(Service Level Agreement)?
为了支撑业务,可预测的扩展性和高可用性是一个生产应用必须展示的运行特性。一些企业需要超过 99.9999% 的正常运行时间,另一些不要求这么高,但所有的应用都需要保证 SLA 规定的可运行性。从预测的角度看,开发一个系统在 99.9% 和 99.9999% 级别是一样困难的。
集群一直以来都是难以解决的问题。在 Spring Web Flow 和普通 web 应用上下文中,这尤其意味着保证用户状态的高可用性和故障恢复能力是满足性能且值得信赖的。在一个节点出现故障(应用服务器或 JVM 崩溃),使用 session 粘滞(这是配置负载均衡最通用的首选方式)是一个开始,但我们需要一个有效手段来无缝的将用户状态从一个节点迁移到另一个节点。
当我们说集群时意味着什么,它和缓存有什么区别?我们使用的集群定义是: 在多个 JMV 间共享应用状态,而缓存可以被定义为: 让应用状态靠近执行上下文。从这个角度看,缓存是集群的一个子集。
我们所认为的企业级 Web/ 企业集群最小解决方案集至少需要包括:
- 可扩展性
- 高可用性
- fail-over
- 性能
- 对已有代码的最小影响
- 简单的部署和配置
- 可见运行时 (监控)
让我们关注一个能解决所有这些看上去无关的需求的解决方案。通常解决一个问题有很多方案,而且市场上也有很多宣称能给 web 应用提供高可用性的产品。Terracotta 就提供了这样一个解决方案。
什么是 Terracotta for Spring?
Terracotta for Spring_ 是基于 _Spring_ 应用的运行时,它为 _Spring 应用提供了透明的高性能集群支持,对应用代码和部署及配置流程影响都很小。它通过在应用下面的堆级别进行集群而不是直接集群应用。
这让开发者能够开发与无状态方式不同的单节点有状态Spring_ 应用。这使得在需要扩展的应用开始设计时不考虑集群。而在应用需要扩展或者要保证搞可用性和故障恢复时,他们只需要在 _Terracotta 配置文件中定义哪些 _Spring_应用上下文中的 beans 需要进行集群。Terracotta for Spring 使得应用能够被自动和透明的集群,还保证在集群间的语义和单节点一样。
对于 _Spring Web Flow_ 来说,这实际上更简单。用户为了获得 web 应用的状态和持续仓库的集群能力, 只需要在 _Terracotta_ 配置文件中把特定的 web 应用声明为启用“session-support”。(详细内容见下面的章节“声明式配置”)
从宏观上看,Terracotta for Spring 提供了:
- HTTP session 状态的集群。保证 _Spring Web Flow_ 中的用户状态和扩展仓库或放入 HTTP session 的其它状态的高可用性和故障恢复能力。
- Spring bean 的集群。Spring_bean 的生命周期语义和域在集群间被保存,它们在“逻辑”上相同的 ApplicationContext 中。目前能被集群的 bean 类型是 _singleton_ 和 _session scoped. 用户可以声明式配置哪个 _application contexts_ 中的哪个 bean 需要被集群。
- 透明集群 POJO。不需要修改已有的代码,甚至不需要源代码。应用基于很少的声明式 XML 配置文件,在载入期透明的生效。Terracotta for Spring 不需要实现 Serializable, Externalizable 或其它接口的类。能这样实现的原因它并没有使用序列化,而只是将实际的差量和已经改变了的数据传输给当前需要的节点 (lazily)。
- 虚拟内存管理。它也提供分布式垃圾收集和虚拟堆功能。比如,由于物理内存在需要时被换入换出,它能在一个 4G RAM 的机器上运行需要 200G 堆的 Web 应用。 这也意味着你不需要关心 _Spring Web Flow_ 会话数据的大小是否超过了物理堆大小。
### 堆层次集群
Terracotta for Spring 使用 aspect-oriented 技术来在类载入时适配应用。在这个阶段它扩展了应用以保证 Java 语义在集群间被正确的维护,包括对象引用,进程调用和垃圾收集等
我们在前面提到 _Terracotta_ 并没有使用 serialization。这意味着任何 _Spring Web Flow_ 维护的会话都可以在集群间共享。这也意味着 Terracotta 并没有把会话状态的全部对象图发给所有节点,而是把图分解成纯粹的数据,并在网络间传输实际的“差量”和改变–数据的“原始信息”在其它节点上。
因为有一个记录节点间相互引用的中心调度器(见下文),它能以 lazy 的方式工作,而只把改变传送到引用了这些“dirty”数据的节点。这需要使用局部引用。如果负载均衡配置为使用"session 粘滞" 就更有效率,因为这意味着很多数据可能永远不会脱离实际的 session 而不用复制到其它节点。
这个架构是中心辐射的,也就是有一个管理客户端的中心调度器。在这里客户端就是你配有 _Terracotta for Spring_ 运行时的普通应用。调度器不是单点失败的,但你可以配置一组备用调度器,并在主调度器崩溃时选择一个来接替。你也可以独立于客户端,对调度器进行集群扩展。
构建一个高可用的有状态 web 应用
这里我们使用一个叫 _Sellitem_ 的示例应用来推动讨论并展示给大家:
- 如何使用 Spring Web Flow 来构建一个有状态基于会话的 web 应用。
- 如何使用 Terracotta for Spring 来声明式集群有状态应用。
_Sellitem_ 示例应用可以在 Spring Web Flow 的发布版本中找到。(更多信息见文章末尾的“Resources” 章节)
使用 Spring Web Flow 实现一个有状态 Web 应用: Sellitem
Sellitem 是展示结合了有条件转移、会话域、流程执行转向和延续性的示例应用。用户在几个页面间导航,可以定义货物的价格、可以销售的货物数量、折扣比率、送货详情(如果需要)和最后查看所有信息。
我们不会通读应用的所有源码,而是主要介绍一些关键概念,如怎么配置 _Spring Web Flow_ 输出的不同标准服务(Bean)和怎么定义页面流转(使用针对页面流转的 _Spring Web Flow_DSL 的 XML 版本)。
在 Spring MVC 中配置 DispatcherServlet
应用的入口是一个标准的 Spring MVC DispatcherServlet,它在 web.xml 中注册并在 web application context 中映射到 *.htm。
<servlet><br></br> <servlet-name>sellitem</servlet-name><br></br> <servlet-class><br></br> org.springframework.web.servlet.DispatcherServlet<br></br> </servlet-class><br></br> <init-param><br></br> <param-name>contextConfigLocation</param-name><br></br> <param-value><br></br> /WEB-INF/sellitem-servlet-config.xml<br></br> /WEB-INF/sellitem-webflow-config.xml<br></br> </param-value><br></br> </init-param><br></br></servlet><p><servlet-mapping></p><br></br> <servlet-name>sellitem</servlet-name><br></br> <url-pattern>*.htm</url-pattern><br></br></servlet-mapping>
DispatcherServlet 配置在 Spring 的配置文件 sellitem-servlet-config.xml 和 sellitem-webflow-config.xml 中。 sellitem-servlet-config.xml 中有一个映射到"/pos.htm"的控制器,它将所有该 URL 的请求转发到 Spring Web Flow 系统(它的入口是一个流程执行器):
<bean name="/pos.htm" class="org.springframework.webflow.executor.mvc.FlowController"><br></br> <property name="flowExecutor" ref="flowExecutor" /><br></br></bean>
### 配置 flow executor 和 flow registry beans
Spring flowExecutor bean 配置使用一个 flowRegistry bean 来执行"/WEB-INF/flows/"目录中的基于 XML 的流转定义。
<flow:executor id="flowExecutor" registry-ref="flowRegistry"/><p><flow:registry id="flowRegistry"></p><br></br> <flow:location path="/WEB-INF/flows/**-flow.xml" /><br></br></flow:registry>
### 定义页面流转
剩余的逻辑在我们已经注册的 flowRegistry bean 的流转定义中。(参照前面的’配置 flow executor 和 flow registry beans 章节)。
在深入流转实现细节前,我们先看一下页面流转的状态图(如下图)。
从上面我们可以看到流转在结束前经过了几个步骤,在决定销售是否需要送货时有一个决策状态。
一个很好的针对上面导航规则的初始流转定义实现如下:
<flow xmlns="http://www.springframework.org/schema/webflow"<br></br> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br></br> xsi:schemaLocation="<br></br> http://www.springframework.org/schema/webflow<br></br> http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd"><p> <var name="sale" class="org.springframework.webflow.samples.sellitem.Sale"/></p><p> <start-state idref="enterPriceAndItemCount"/></p><p> <view-state id="enterPriceAndItemCount" view="priceAndItemCountForm"></p><br></br> <transition on="submit" to="enterCategory"/><br></br> </view-state><p> <view-state id="enterCategory" view="categoryForm"></p><br></br> <transition on="submit" to="requiresShipping"/><br></br> </view-state><p> <decision-state id="requiresShipping"></p><br></br> <if test="${flowScope.sale.shipping}" then="enterShippingDetails" else="finish"/><br></br> </decision-state><p> <view-state id="enterShippingDetails" view="shippingDetailsForm"></p><br></br> <transition on="submit" to="finish"/><br></br> </view-state><p> <end-state id="finish" view="costOverview"/></p><p></flow></p>
我们从上面的定义可以看到,实际状态与状态图中的状态对应,状态转换与图中的箭头对应。“sale” bean 是流转开始时分配的流转变量实例。它持有了 Sale 相关的属性。
上面的定义展示了所有的导航逻辑,但还没有实现任何应用行为。特别是在用户提交时更新 Sale Bean 的逻辑还没有实现。另外后台的 sale 处理逻辑还没有定义。
实现了所有必需行为的 _ 完整 Spring Web Flow_ 定义如下:
<flow xmlns="http://www.springframework.org/schema/webflow"<br></br> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br></br> xsi:schemaLocation=" http://www.springframework.org/schema/webflow<br></br> http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd"><p> <var name="sale" class="org.springframework.webflow.samples.sellitem.Sale"/></p><p> <start-state idref="enterPriceAndItemCount"/></p><p> <view-state id="enterPriceAndItemCount" view="priceAndItemCountForm"></p><br></br> <render-actions><br></br> <action bean="formAction" method="setupForm"/><br></br> </render-actions><br></br> <transition on="submit" to="enterCategory"><br></br> <action bean="formAction" method="bindAndValidate"><br></br> <attribute name="validatorMethod" value="validatePriceAndItemCount"/><br></br> </action><br></br> </transition><br></br> </view-state><p> <view-state id="enterCategory" view="categoryForm"></p><br></br> <transition on="submit" to="requiresShipping"><br></br> <action bean="formAction" method="bind"/><br></br> </transition><br></br> </view-state><p> <decision-state id="requiresShipping"></p><br></br> <if test="${flowScope.sale.shipping}" then="enterShippingDetails" else="processSale"/><br></br> </decision-state><p> <view-state id="enterShippingDetails" view="shippingDetailsForm"></p><br></br> <transition on="submit" to="processSale"><br></br> <action bean="formAction" method="bind"/><br></br> </transition><br></br> </view-state><p> <action-state id="processSale"></p><br></br> <bean-action bean="saleProcessor" method="process"><br></br> <method-arguments><br></br> <argument expression="flowScope.sale"/><br></br> </method-arguments><br></br> </bean-action><br></br> <transition on="success" to="finish"/><br></br> </action-state><p> <end-state id="finish" view="costOverview"></p><br></br> <entry-actions><br></br> <action bean="formAction" method="setupForm"/><br></br> </entry-actions><br></br> </end-state><p> <import resource="sellitem-beans.xml"/></p><p></flow> </p>
在定义导航逻辑之外,也定义了适时调用恰当应用行为的 action。这包括处理用户提交事件和调用后台处理器来处理 sale 的逻辑。
Form 绑定和验证
当进入展示表单的视图状态时,流转调用一个 FormAction command bean 来进行表单的装配和提交逻辑。在提交时,FormAction 把用户的请求参数绑定到相应的 sale 属性上并同时验证它们。
<bean id="formAction" class="org.springframework.webflow.action.FormAction"><br></br> <property name="formObjectName" value="sale"/><br></br> <property name="validator"><br></br> <bean class="org.springframework.webflow.samples.sellitem.SaleValidator"/><br></br> </property><br></br></bean>
### 更多信息
Spring Web Flow 全部的代码、文档和 10 个示例应用(包括 sellitem)都可以在 Spring 网站上找到 http://www.springframework.org/webflow 。
集群 Sellitem 应用
现在我们已经看过了如何使用 _Spring Web Flow_ 来实现一个有状态 web 应用。接下来让我们更高兴的看如何给我们的示例应用启用高可用性和故障恢复,如何使用 Terracotta for Spring 进行集群来提供透明容错性和在节点间共享状态。
听上去很难? 你会发现这实际上很简单。
声明式配置
Sellitem_ 示例应用使用一个 Sale 类的实例来保存当前销售的会话数据;同样 _Spring Web Flow executor repository 也使用 HTTP session 来保存所有的会话数据。
要使用 Terracotta for Spring,我们需要确保为给定的 web 应用启用了 HTTP session 集群,包括所有可能被保存在 HTTP session 中(或者能被保存在 HTTP session 中的实例引用)的类,以便于检测。这里是 Terracotta for Spring 的 tc-config.xml 配置文件的一个例子:
<application><br></br> <spring><br></br> <jee-application <strong>name="swf-sellitem"</strong>><p><strong><session-support>true</session-support></strong> <instrumented-classes></p><br></br> <include><br></br> <class-expression><p><strong>org.springframework.webflow.samples.sellitem.Sale</strong> </class-expression></p><br></br> </include><br></br> </instrumented-classes><br></br> </jee-application><br></br> </spring><br></br></application>
这里我们为 swf-sellitem WAR 文件启用了 HTTP session 集群并配置了 Sale 类。
就是这样,我们已经做了很多了。
启用 Terracotta
我们唯一需要做的就是在应用中启用 Terracotta for Spring 运行时。这可以通过修改 Tomcat web 服务器的启动脚本并在脚本最前面加入下面的环境变量完成:
set JAVA_OPTS=-Xbootclasspath/p:"%DSO_BOOT_JAR%"<br></br>set JAVA_OPTS=%JAVA_OPTS% -Dtc.install-root="%TC_INSTALL_DIR%"<br></br>set JAVA_OPTS=%JAVA_OPTS% -Dtc.config="%LOCAL_DIR%\tc-config.xml"
这里面:
- DSO_BOOT_JAR 环境变量指向 jar 的根目录 (能在 Terracotta for Spring 安装的根目录的 common/lib/dso-boot 下找到)。
- TC_INSTALL_DIR 环境变量指向 Terracotta for Spring 安装的根目录。
- LOCAL_DIR 指向包含 tc-config.xml 的目录.。
Sellitem 应用预配置了 _Terracotta for Spring_ 集群的代码可以在下面的’Resources’章节找到。也包括了开箱即用的 Tomcat 集群配置。
注解:_Spring_ 应用上下文中的集群 bean 可以在服务 (bean) 级别配置,并依赖于 _Terracotta for Spring_ 的 auto-include 检测机制. 例如,大多数情况下我们不需要关心引入哪个类,而只需要在 tc-config.xml 文件中定义希望集群的 bean 的名称。
总结
Spring Web Flow 给包括文章中看到的这种简单应用到有很多页面流转的大型企业应用,都提供了构建基于会话的有状态应用的有力手段。 _Terracotta for Spring_ 给你的 Spring Web Flow 提供了高可用性。
简而言之, Terracotta for Spring 提供了:
- 给基于 Spring Web Flow 的应用包括普通 Spring 的应用提供容错性。
- 不需要实现java.lang.Serializable,在多个节点间分布的应用中透明共享状态。
- 在多个分布式节点进行资源调用。
- 在多个分布式节点间保持了 Pass-by-reference 语义。
- 声明式配置基本上不用修改现有代码 (除了以前是无状态 Spring 应用,现在需要变成有状态)
Spring Web Flow 和 Terracotta for Spring 结合在一起,给你提供了构建有状态、基于会话、可扩展和高可用性 web 应用的新方式。
资源
Spring Web Flow
主页、下载和文档: http://www.springframework.org/webflow 。
示例应用:在文章中使用的 _Sellitem_ 应用包含在 _Spring Web Flow_ 发布版本中。
Sellitem 应用在线演示: http://spring.ervacon.com/swf-sellitem/ 。Terracotta for Spring
Terracotta for Spring 是提供 JVM 级集群开源项目 Open Terracotta 的一部分: http://terracotta.org/ 。
下载: http://terracotta.org/confluence/display/orgsite/Download
示例应用:Sellitem 应用包含了 Terracotta for Spring 简化版本的发布包,它已经预配置了且能直接运行(甚至打包了 Tomcat 和负载均衡)。
文档: http://terracotta.org/confluence/display/docs1/Spring+Quick+Start 。查看英文原文: Web Applications with Spring Web Flow and Terracotta for Spring
作者简介: Jonas Bonér 在 Terracotta Inc. 工作,他关注于战略、产品开发与架构和传播技术。他是 AspectWerkz AOP 框架的创始人,也是 Eclipse AspectJ 5 项目和很多其它开源项目的贡献者。他经常做 AOP、JVM-level 集群和其它新兴技术的发言。 Eugene Kuleshov 是一个独立顾问。他有 12 年的软件设计和开发经验,专注于应用安全、企业集成 (EAI) 和面向消息中间件。他积极参与了很多开源社区的项目。
译者简介:张俊,网名 Pesome,上海交通大学软件工程硕士,多年 JavaEE 开发经验,积极参与国内开源组织。个人用 Spring、Hibernate 等开源软件搭建 www.openfans.net 网站,以 Web 2.0 的形式介绍开源软件,希望为中国的开源软件事业做点贡献。与 InfoQ 中文站分享内容,请邮件至 china-editorial@infoq.com 。
评论