《Java EE 7精粹》—— 3.10 Faces Flow

    xiaoxiao2024-03-29  8

    本节书摘来异步社区《Java EE 7精粹》一书中的第3章,第3.10节,作者:【美】Arun Gupta,更多章节内容可以访问云栖社区“异步社区”公众号查看。

    3.10 Faces Flow

    JSF2.2引入了Faces Flow。这个功能借用了ADF Task Flows、Spring Web Flow和Apache MyFaces CODI的核心概念,使其标准化为JSF2.2的一部分,为在应用程序中定义控制流程,提供一种模块化的方法。

    Faces Flow将相关网页和相应的后台Bean封装成一个模块。该模块具有定义良好的入口点和出口点,可以由应用程序开发人员来分配。通常在Faces Flow中的对象被设计为允许用户来完成的任务,该任务需要在若干不同视图输入信息。应用程序也因此成为流而不仅仅是视图的集合。

    试想有一个多页面的购物车,第一个页面用于选择项目,第二个页面用于选择配送,第三个页面用于输入信用卡详细信息,第四个页面用于确认订单。可以使用托管Bean去捕获数据,会话作用域的变量在页面之间传递信息,点击按钮来调用后台EJB的业务逻辑,以及从一个页面到另一个页面的(有条件的)导航规则。这样的实现会有如下问题。

    这种流的顺序通常是一个更大的应用程序的一部分。该应用程序通常有很多页面,是一个巨大的流而且一切都是全局可见的,无法逻辑分区。页面或视图的流不能被封装为一个逻辑单元,因此不能被重复使用,不容易并入另一个更大的应用程序或流。同样的流不能在多个窗口打开,因为会话作用域变量是在页面之间传递信息的。CDI定义了@ConversationScoped,但那只是解决方案的一部分。基于请求作用域服务于特定的请求,基于会话作用域大于请求作用域,但是在浏览器关闭后会无效。我们需要一种作用域可以跨越每个应用逻辑的多个页面,这正是所缺的。最小的逻辑粒度是页面。调用应用逻辑的唯一方法就是把它绑到页面中的由用户激活的UI组件。在没有任何用户启动的操作之前,业务逻辑不能被调用。

    Faces Flow提供了以下针对这些问题的解决方案。

    应用程序被分解成一系列模块化的流,他们之间可以相互调用。这些页面的流可以打包为模块,可以在相同或完全不同的应用程序中被重用。共享内存作用域(例如,流作用域)允许数据在task流中的视图之间传递。可以在Bean中指定新的CDI作用域@FlowScoped。这使得在进入/退出作用域时,自动激活/去活Bean。业务逻辑可以从基于流定义的页面中的任何地方被调用。

    应用程序的流不再局限于在页面之间流动,而是被定义为在“节点”之间流动。有以下五种不同类型的节点。

    视图(View):应用程序中的任何JSF页面。方法调用(Method call):通过一个EL表达式从流图调用应用逻辑。切换(Switch):导航决定基于布尔EL表达式的流图。流调用(Flow call):通过参数调用另一个流并接收返回值。流返回(Flow return):返回到调用流。

    这些节点定义流的入口点和出口点。

    新引入的CDI作用域@FlowScoped定义了指定流中Bean的作用域。这使得当进入/退出作用域时,自动激活/去活Bean:

    在这段代码中,这个Bean有两个流作用域的变量:address和creditCard。Bean定义的流为flow1。

    还引入新的流存储EL对象,#{flowScope}。该流存储对象映射到facesContext.getApplication().getFlowHandler().getCurrentFlowScope():

    在这段代码中,文本框中输入的值和#{flowScope.value}绑定,这个EL表达式的值能够被流中的其他页面访问。

    可以使用以声明方式定义流,或使用FlowBuilder API以编程方式定义流。这两种机制是互斥的。

    流可以打包在JAR文件或目录中。JAR包要求流在JAR文件中的META-INF/faces-config.xml中显式地声明。流节点封装在META-INF/flows/ ,其中是一个JAR目录项,名字是对应的FlowDefinition类中指定的流id。如果@FlowScoped Bean和通过FlowBuilder定义的流被打包在JAR文件中,他们必须附有META-INF/beans.xml:

    在这个JAR包中:

    有flow1和flow2两个流。META-INF/beans.xml需要启用CDI。如果Bean定义的注解在归档中使用,可能隐式启用。MyFlow1Bean和MyFlow2Bean是流作用域的Bean。这些Bean被用于存储任何流的本地数据。MyFlow1Definition定义了一个流的入口点和出口点,从另一个流进来的参数名称和值,到另一个流去的参数名和值,以及到其他节点的导航:

    在这段代码中:

    流是通过CDI生产者以编程方式定义的。@FlowDefinition是一个类级别的注解,使流通过流畅的流生成器API来定义。一个FlowBuilder实例是通过@FlowBuilderParameter注入作为参数,用于定义流。flow1被定义为流标识符,flow1.xhtml被标记为起始节点。returnNode方法用于定义流的出口点。在本例中,流由goHome动作定位到/index。节点值可以被指定为一个EL表达式,例如,“可以被绑定到一个Bean”。命名为传入的参数是来自另一个流命名参数,通过inboundParameter方法定义。该方法的值使用对应的outboundParameter元素填充。该值通过#{flowScope}存储在流本地存储。flowCallNode方法用于定义流的出口点。在本例中,flow2流被调用。命名为传出的参数及其值是通过outboundParameter方法设置的。

    同样地,MyFlow2Definition类定义flow2。

    流打包在目录中,使用约定优于配置。这些约定是:

    每个视图声明语言文件,由.xhtml页面定义,在该目录中是该流的视图节点。流的起始节点是与流相同名称的视图。在目录中的任何视图之间的导航被认为是在流中。导航到该目录以外的视图被认为是一个流的出口点。可选的-flow.xml文件代表流的定义。在这个文件中的规则会覆盖刚才所描述的约定:

    遵循方才定义的约定,在这个目录中:

    flow1.xhtml是流flow1的起始节点,flow1a和flow1b是同一流中的另外两个节点。flow2.xhtml是流flow2的起始节点,flow2a和flow2b是同一流中的另外两个节点。flow2/flow2-flow.xml定义了流flow2的声明式导航。定义了流的入口点和出口点,来自另一个流的输入参数名称和值,去向另一个流的输出参数名称和值,和到其他节点的导航:

    在这段片段中:

    通过id属性为flow1定义了流。定义了流的出口点。在本例中,流由goHome动作定位到/index。节点值可以被指定为一个EL表达式,例如,“可以被绑定到一个Bean”。定义了传入参数的名称,该方法的值使用相应的元素填充,并通过#{flowScope}存储在流本地存储。定义了流的出口点。在本例中,flow2流被调用。定义了传出参数的名称及其值。

    WEB-INF目录将包含页面所需的其他资源,比如backing bean。

    最新回复(0)