Struts是一个用来构建企业级J2EE应用程序的流行框架。通过使用Struts,J2EE Web应用程序的开发变得更加轻松且更易于管理。Beehive是Apache软件基金会(Apache Software Foundation,ASF)的一个开源项目,它在Struts的基础上构建一个简单的页面流(Page Flow)模型,使Web应用程序的开发变得更加简单明了。通过使用新的 JSR-175和 JSR-181元数据注释功能,Beehive减少了J2EE应用程序开发的编码量。本文将介绍Beehive页面流技术,以及如何使用它来提高Struts软件的生产力和质量。本文还介绍了如何将该技术迁移到普通的Struts应用程序中去。本文假定读者对Struts有一定的了解。
Struts是Apache软件基金会的Jakarta项目的一部分,是一个基于模型-视图-控制器(Model-View-Controller,MVC)设计模式构建Web应用程序的开源框架。Struts使用动作(action)类来构建框架的控制器组件。典型的Struts应用程序要求使用大量动作类来处理某一流程流(process flow)的几个动作,并要求使用一个XML配置文件来声明跳转。因此,像数据管理和维护之类的问题已经成为现有Struts应用程序的一个主要关注点。图1显示了一个示例应用程序的流程图,意在对Struts和页面流作一比较。
图1. LoginProcessFlow应用程序的流程图
在Struts中开发该应用程序的时候,通常要求针对每个动作(begin、signup、login和logout)使用动作类。清单1显示了一个典型的动作类,在本例中是图中描绘的begin动作。
清单1. BeginAction.java
为完成该Struts应用程序,需要针对signup、login和logout使用类似的动作类。HTTP会话被用来跨动作传递数据,这使得管理和维护数据变得很困难。Struts使用一个配置文件(struts-config.xml)来声明到JSP页面的跳转。因此,要更改业务规则通常还需要更改动作类和配置文件。具体来说,更改业务逻辑要求更改动作,而更改业务流程则要求更改配置文件(通常二者都需要更改)。清单2显示的是LoginProcessFlow应用程序的典型的struts-config.xml文件。
页面流
了解开发Struts应用程序的复杂性后,让我们来深入研究页面流及其提供的功能。Beehive的NetUI页面流框架使得开发和维护工作更加轻松和易于管理。清单3是一个页面流控制器(通常简写为Java页面流(Java Page Flow,JPF)),用于前面所述的LoginProcessFlow应用程序。页面流框架使用一个JPF文件来代替所有的动作类和配置文件。
清单3. LoginPageFlowController.java
package comparison.pageflows;
import org.apache.beehive.netui.pageflow.PageFlowController;
import org.apache.beehive.netui.pageflow.Forward;
import org.apache.beehive.netui.pageflow.annotations.Jpf;
public class LoginPageFlowController extends PageFlowController
{
@Jpf.Action(
forwards = {
@Jpf.Forward(name = "success", path = "/index.jsp")
}
)
protected Forward begin()
{
//initial processing
return new Forward("success");
}
@Jpf.Action(
forwards = {
@Jpf.Forward(name = "success", path = "/mypage.jsp"),
@Jpf.Forward(name = "failure", path = "/index.jsp")
}
)
protected Forward login()
{
//authentication logic
if(user_authenticated)
return new Forward("success");
else
return new Forward("failure")
}
@Jpf.Action(
forwards = {
@Jpf.Forward(name = "success", path = "/confirm.jsp")
}
)
protected Forward signup()
{
return new Forward("success");
}
@Jpf.Action(
forwards = {
@Jpf.Forward(name = "success", path = "/begin.do")
}
)
protected Forward logOut()
{
return new Forward("success");
}
}
清单3清楚地显示了Struts和页面流所要求的开发工作量和维护任务方面的差别。
如前所述,JPF是注释驱动的,并且不需要在配置文件中声明跳转。页面流利用Beehive框架自动生成基于注释的配置文件(如:JPF中声明的 @Jpf.Action)。清单3显示了如何在一个JPF文件中处理所有的动作以及以注释的形式声明的跳转。例如begin动作,如果成功的话,就跳转到index.jsp页面。您可以清楚地看到动作如何处理逻辑,以及简单的注释如何处理跳转,这些注释在使用前要进行声明。注释可以在动作级别声明,也可以在页面流级别声明。在Beehive提供的众多注释中,@Jpf.Forward、@Jpf.Controller和 @Jpf.Action是最常用的。对所有注释的讨论不在本文的范围之内。要获得有关的更多信息,请访问 Page Flow Annotations说明文档。
Struts应用程序要求对每个动作使用一个动作类,而使用页面流开发应用程序只需一个JPF文件就足够了。页面流提供一种简单的、单一文件的编程模型。在构建复杂的Web应用程序时,页面流使开发人员能够立即开始开发,而无需经历与学习Struts有关的曲折过程。当遇到因为业务规则的更改而引起的修改时,页面流显得更为灵活。
会话数据管理
页面流提供的编程框架添加了多处改进,刚刚就是注释驱动的自动生成和同步XML配置文件的例子。接下来让我们看看页面流的另一个优点:使用页面流级的(Page Flow-scoped)变量可以更轻松地跨多个动作管理和维护数据。
在Struts中,动作类获取会话对象并设置属性(或赋值),其后其他动作会对这些属性进行检索。清单4显示的例子有两个Struts动作类:LoginAction和LogOutAction,它们使用HTTP会话共享一个变量username。
清单4. LoginAction.java和LogOutAction.java
package comparision.struts;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public final class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
String username = form.getUserName();
// get this session
HttpSession session = request.getSession();
session.setAttribute("username", username);
return (mapping.findForward("login"));
}
}
package comparision.struts;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public final class LogOutAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
// get this session
HttpSession session = request.getSession();
String username = session.getAttribute("username");
//logic to log out user
return (mapping.findForward("logout"));
}
}
上面的代码清楚地显示了LoginAction和LogOutAction显式地共享username变量的方式,在本例中是使用HTTP会话。跨多个动作类管理这些会话变量可能会相当复杂。
相比之下,页面流提供一个以JPF级别声明的简单变量,它可以跨所有的页面流动作共享数据。在清单5的例子中,以页面流级别声明了一个String变量(username),并在login和logout动作中使用它。
清单5. LoginPageFlowController.java
package comparision.pageflows;
import org.apache.beehive.netui.pageflow.PageFlowController;
import org.apache.beehive.netui.pageflow.Forward;
import org.apache.beehive.netui.pageflow.annotations.Jpf;
public class LoginPageFlowController extends PageFlowController
{
private String username;
@Jpf.Action(
forwards = {
@Jpf.Forward(name = "success", path = "/index.jsp")
}
)
protected Forward begin()
{
//intial processing
return new Forward("success");
}
@Jpf.Action(
forwards = {
@Jpf.Forward(name = "success", path = "/mypage.jsp"),
@Jpf.Forward(name = "failure", path = "/index.jsp")
}
)
protected Forward login(ProfileForm form)
{
username = form.getUsername();
//authentication logic
if(user_authenticated)
return new Forward("success");
else return new Forward("failure")
}
@Jpf.Action(
forwards = {
@Jpf.Forward(name = "success", path = "/confirm.jsp")
}
)
protected Forward signup()
{
return new Forward("success");
}
@Jpf.Action(
forwards = {
@Jpf.Forward(name = "success", path = "/begin.do")
}
)
protected Forward logOut(ProfileForm form)
{
logout(username);
return new Forward("success");
}
}
如您所见,共享变量的处理方式简单直观。
页面流的优点
除了易于使用和其他优点之外,这里还列出了页面流的一些重要优点:
- 页面流是模块化的:简单的Web应用程序可能包括几个页面流,每个页面流处理一部分逻辑相关的功能。可以使用共享页面流或全局页面流来处理异常和未处理的动作。
- 页面流是可嵌套的:页面流嵌套提供细粒度、可重用的模块化,以处理功能和数据。
- 页面流是有状态的:由于可以声明页面流级的变量,所以数据管理非常容易。
- 页面流是注释驱动的:最后但并非最不重要的是,注释驱动的编程是一个受欢迎的改变,它替代了多文件的、分散代码的编程。
尽管本文仅仅是介绍一些基本知识,但是我希望读者能对这些特性如何有助于创建可管理的大型应用程序有所了解。模块化和嵌套特性对于开发可重用的Web应用程序组件特别有用,而诸如会话数据管理和丰富的数据绑定标签之类的特性则有助于简化编程。
集成Struts和页面流
我希望前面的一节能够说明页面流是Struts的一个有用扩展。现在,让我们来看一看用什么方法能够把页面流的功能添加到现有Struts项目中,或者如何使Struts和页面流应用程序能够互操作。
页面流构建于Apache Struts 1.1的基础之上。每个页面流都被编译为一个Struts模块。因此,页面流和Struts 1.1应用程序可以紧密合作。
有两种在页面流应用程序中使用Struts组件的方法:
- 利用Struts互操作性:在页面流应用程序中直接使用Struts组件。
- 利用Struts合并功能:将简单的Struts应用程序导入到新的页面流应用程序中。
集成:Struts互操作性
Struts互操作性功能允许用户使用现有的基于Struts的组件但不修改它们。这允许现有的Struts组件与页面流组件交互,并可以充分利用现有的JSP页面、动作类、表单bean和流配置。
Struts和页面流应用程序可以在一个Web应用程序中共存并交互。要从页面流跳转到(纯)Struts模块,只需引用Struts模块中对应的动作即可。反之亦然:在Struts模块中,只需配置一个指向页面流中对应方法的动作,就可以跳转到页面流。
页面流可以用作前端控制器处理初始请求,然后把控制权转交给Struts中的动作,这些动作进行某种处理之后会将控制权再传回给页面流。在嵌套页面流中可以看到类似的模式。也可以用这种方式在Struts应用程序和页面流之间共享表单bean。
- 在页面流中,可以导入并使用由Struts 1.1模块定义的表单bean。因此用户可以快速地利用现有的Struts代码,而不必修改Struts动作类和表单bean。
- 在Struts 1.1动作类中,可以导入和使用页面流定义的表单bean。
包含Struts模块和页面流(如果它们要进行互操作的话)的文件必须在同一个Web项目中。
对Struts模块和页面流的要求
这里是对将在同一个Web项目中进行互操作的Struts模块和页面流的要求:
- 现有的Struts应该是版本1.1。
- 包含Struts模块的文件必须和页面流属于同一个Web项目。
- JAR文件中所有已编译的、与Struts相关的类(字节码)必须存放在WEB-INF/lib目录下。
- 为避免命名冲突,Struts模块名称和页面流目录名称在整个Web项目中必须是惟一的。
- 同一个页面流目录下的JSP页面总是在页面流JPF上下文中呈现。调用动作和呈现在Struts模块上下文中的JSP页面应当存放到其他目录下,比如/strutsModule目录下。
- 如果页面流控制器类和Struts动作类共享同一表单bean,可以将表单bean定义为内部类,也可以将其定义为外部Java文件。将表单bean类导入页面流JPF和将要使用它的Struts动作类中。
- 在共享表单bean时,Struts模块的XML文件中 属性值必须与页面流的jpf-struts-config-.xml文件中生成的name属性值精确匹配。
- 默认情况下,页面流的范围是从表单bean实例到请求。共享数据的最容易的方式就是在Struts和页面流之间传递会话级(session-scoped)表单bean。这可以通过使用Struts合并功能将(为页面流生成的)Struts配置XML合并到会话级表单bean中来实现。
- 如果一个Web项目综合使用了页面流和Struts模块,必须通过编辑项目的 /WEB-INF/web.xml文件注册每个Struts模块。
互操作性的一个例子
本例显示了页面流动作如何调用Struts动作以及Struts动作如何调用页面流动作。本例的目的仅仅是显示互操作性,因此代码并不完整。PageFlowActionA将控制权和会话级表单bean传递给StrutsActionA。StrutsActionA进行某种处理并跳转到Page.jsp,后者包含一个Struts动作StrutsActionB。StrutsActionB将控制权传回PageFlowActionB。
集成:Struts合并
Struts合并可以将简单的Struts应用程序转换为页面流应用程序。这种方法是将简单的Struts应用程序迁移到页面流框架中的一种快捷方式。
Struts合并功能不同于Struts互操作功能。对于Struts合并功能,可以在控制器级别指定Struts XML配置文件,如下:
/**
* @jpf:controller struts-merge="/WEB-INF/struts-config-merge-example.xml"
*/
public class ExampleStrutsMergeController extends PageFlowController
{
...
}
这一步允许现有的纯Struts的XML配置文件与页面流的生成的jpf-struts-config-.xml文件合并(在项目编译时)。Struts合并功能的目的是重写页面流的默认值,或者为页面流指定页面流注释或其属性不提供的设置。Struts合并文件通常很小,而且仅仅修改页面流及其动作和表单bean。尽管可以将动作映射添加到Struts合并文件中,但并不推荐这种实践。Struts动作映射应当使用 @jpf注释在页面流的 .jpf文件中声明,然后(如果需要的话)使用Struts合并文件修改。
还可以使用struts合并功能,将纯Struts应用程序中的配置数据读入页面流应用程序的配置文件。一般来说,页面流的配置文件完全由应用程序的Java源文件(具体地说是由散布于控制器文件的元数据注释)生成。但是,如果要把Struts模块集成到应用程序中,配置文件既可以由Java源文件生成,也可以由Struts模块的配置文件生成。如果是后者,就可以在生成的配置文件中灵活地更改或添加任何标签。例如,可以将一个动作表单的默认作用域从请求级重写为会话级。为此,只需创建重写页面流配置文件的Struts配置文件的适当部分,然后从页面流的Java源文件中引用这个重写后的文件,正如上面的例子所示。
考虑到诸如现有Struts的复杂性、时间和工作量等因素,可以在页面流中综合使用Struts合并功能和Struts互操作性功能,以取得最佳效果。
Beehive
Apache Beehive Project(蜂巢计划)不只包含页面流。它有三个子项目:
- NetUI PageFlows(NetUI页面流):本文中介绍的Web应用程序框架。
- Controls:一个帮助程序员构建组件(如:JavaBean,它将元数据合并到它的编程模型中)的轻量级组件框架。该项目也提供一些预制的控件。
- Web Services:一个针对JSR-181规范所描述的Web services的注释驱动的编程模型。
Java页面流全面支持控件,控件是访问企业资源和包装业务逻辑的简单的编程模型抽象。Java控件使开发人员可以在一个简单直观的环境中使用方法、事件和属性访问J2EE平台的强大功能(安全性、事务和异步),而且能够构建遵从面向服务架构(SOA)的最佳实践的Web应用程序。例如,可以在页面流中使用一个简单的注释来声明一个Web service控件,如下所示: @Control
public MyWebService myWebService;
环境会自动将控件插入页面流中。
与XMLBeans相结合,Beehive就可以提供无缝地构建和部署企业SOA的基础架构。Beehive 1.0预定在本月底发布,2.0的规划正在进行中。可以使用诸如BEA WebLogic Workshop之类的IDE或Pollinate项目中开发的Eclipse插件来开发Beehive应用程序。
结束语
页面流非常坚定地承诺要使企业软件的开发和维护变得更加轻松快捷。在本文中,我们介绍了页面流超越Struts的一些优点、页面流如何增强现有的Struts应用程序,以及可以用于升级和无缝构建企业级SOA业务应用程序的几种方法,包括Struts互操作性和Struts合并。
参考资料
原文出处
http://dev2dev.bea.com/pub/a/2005/07/pageflows.html