为JSF定做的应用程序框架(三)

xiaoxiao2024-05-07  26

页面动作来帮忙   Seam 的页面动作可以帮助您预先拦截呈现的假信号。页面动作是使用方法绑定指定的,方法绑定在进入页面时、Render Response 阶段之前执行。对于 /WEB-INF/pages.xml 配置文件中一个给定的视图 ID,可以配置任意数量的方法绑定。(或者,可以通过将它们放在视图模板邻近的一个文件中,复制它的名称,但是将文件扩展名换为 *.page.xml,从而分解每个页面的定义)。对于页面动作,XML 是有必要的,因为视图 ID 非常容易变化。就像 JSF 通过 Apply Request Values 阶段的值绑定将 post 数据映射到模型对象一样, Seam 可以通过执行页面动作之前的值绑定将任意请求参数映射到模型对象。这些请求参数注入的配置嵌套在页面动作 XML 声明中。如果页面动作方法调用返回一个非空字符串值,则 Seam 将其当作一个导航事件。因此,不必迁移到一个完整的基于动作的框架中,仍然可以比得上最特别的特性。Seam 包括很多内置的页面动作,它们通常跨应用程序使用。其中包括用于验证 conversation 是否建立的一个动作;可以启动、嵌套和结束 conversation 的动作;处理预期异常的动作;以及确保适当的凭证的动作。   页面动作是启用对 JSF 的书签支持的关键。Seam 的创立者允许在进入页面时请求参数 actionMethod 触发一个方法调用,从而利用了这一特性。更妙的是,您不需要做任何额外的工作就能为书签创建链接。 Seam 提供了两个组件标记:s:link 和 s:button,用以处理细节。这两个标记分别对应于 JSF 中的 h:commandLink 和 h:commandButton。不同之处在于,Seam 组件标记组装的链接使用一个 HTTP GET 操作发出请求,而不是使用 JSF 的 HTTP POST 表单提交模型表示。因此,Seam 创建的链接对书签更 “友好”,对于开发人员来说更方便。   您可能还注意到,当使用页面动作时,地址栏中的 URL 对应于正在显示的页面,而不总是背后的一个页面。(后一种情况之所以会发生,是因为 JSF 将表单配置为 post 回生成它们的 URL。地址栏没有更新,以反映执行动作后的新视图,因为 JSF 通过一个服务器端重定向使之前进。)如果您想演示页面动作的灵活性,那么可以使用它们来创建 RESTful URL(例如 /faces/product/show/10)。为此,将页面动作方法映射到视图 ID“/product/show/*”,其中 /faces 前缀是 JSF servlet 映射部分。然后,该页面动作方法利用请求 URL,以判断数据类型和数据标识符,加载数据,然后导航到适当的模板。这个例子很明显地演示了 JSF 请求 URL 与视图模板之间并不一定存在一对一的关系。   工厂组件   JSF 最大的一个失败是没有在用户触发的动作或动作监听器方法以外的其他地方提供可靠的机会来为视图准备数据。将逻辑放在一个动作方法中并不能保证该逻辑在视图呈现之前得到执行,因为页面视图并不总是在用户触发的事件之后。   例如,当一个 JSF 应用程序的 URL 第一次被请求时,会发生什么情况?如果需要在该页面上显示从服务层获得的一组数据,那么在 JSF 生命周期中始终没有好的机会来取数据。您可能会认为,可以将逻辑放在映射到视图中值绑定表达式的 backing bean 的 getter 方法中。但是,每当 JSF 解析那个表达式时,就会触发另一个调用,新的调用又会访问服务层。即使页面上只有少数几个组件,getter 方法也可能被推后数次执行。显然,就性能而言这不是最优的。即使通过使用受管 bean 上的私有属性维护状态,每当面对那样的情况时,仍然必须增加额外的管道。一种解决方案是使用 Seam 的页面动作。但是由于这种任务是如此常见,Seam 提供了一个更加容易的解决方案。   Seam 引入了工厂数据提供者(factory data provider)的概念,工厂数据提供者由 @Factory Java 5 注释指定。虽然有两种方法配置工厂,但是最终结果是同样的数据只需在第一次被请求时准备一次。 Seam 确保随后对相同数据的请求将直接返回之前创建的结果集,而不必再一次触发对查找方法的调用。通过与 conversation 相结合,工厂数据提供者成为实现数据短期缓存的非常强大的特性,否则,取这些数据的代价可能较高。在 JSF 不负责减少它解析一个值绑定表达式的次数的情况下,Seam 的工厂特性常常变得非常方便。   有状态 conversation   关于 JSF 很容易引起困惑的一个地方是它的状态管理功能。JSF 规范解释了在接收一个动作之后页面是如何 “恢复(restored)” 的,在此期间时间事件要进行排队,选择要注册。仔细研究规范中的用词可以发现,虽然在 postback 上恢复了组件树,但是那些组件使用的 backing bean 数据并没有被恢复。组件只是以字符串文字的形式存储值绑定(使用 #{value} 语法的 EL 表达式),只在运行时解析底层数据。这意味着如果一个值是短期作用域存储的,例如页面或请求作用域,那么当 JSF 生命周期到达 Restore View 阶段时,这个值将消失。   不将值绑定数据存储在组件树中的一个最值得注意的不利方面是虚幻事件效果(见 参考资料),这是由 UIData 家族中的临时父组件导致的。如果一个值绑定表达式引用的模型数据不再可用,或者在组件树被恢复之前发生了更改,那么组件树的一些部分将被撤销。如果在这些被撤销的分支中,有一个组件中触发了一个事件,那么它将不能被发现,而且这种事件丢失情况是难于觉察的。(只是队列开发人员可能会惊呼 “为什么我的动作没有被调用?”)   虽然丢失的事件看上去像是异常状况,但并不会导致 JSF 生命周期中出现红色标志。因为这些组件依赖底层数据,以保持稳定和适当地被恢复,所以 JSF 难于知道丢失了什么。   不幸的是,JSF 规范天真地引导开发人员将大多数 backing bean 放入 conversation 作用域中 —— 甚至可以在 “方便的” 作用域内调用它。然后,服务器管理员则必须处理由此导致的 “内存溢出” 错误,集群环境中的服务器相似性,以及服务器重启时的串行化异常。MyFaces Tomahawk 项目通过 t:saveState 标记的形式提供了对虚幻事件的一个解决方案。MyFaces 标记允许将数据(包括整个 backing bean)存储为组件树的一部分,而仅仅是值绑定。然而,这种解决方案有些简陋,很像使用隐藏的表单字段在请求之间传递值。它还造成视图与控制器之间紧密耦合。Seam 的创立者意识到,Java Servlet 规范中三个内置的上下文(请求、会话和应用程序)不足以构成数据驱动的 Web 应用程序的全部作用域。在 Seam 中,他们引入了 conversation 作用域,这是一个有状态作用域,由页面流的起止点界定。   Seam 的 conversation 作用域   “Seam 强调使用有状态组件。” Seam 参考文档中的这句话体现了 Seam 的核心思想。很长一段时间内,关于 Web 应用程序的看法是,它们是无状态的 —— 这种思想一定程度上要归因于 HTTP 协议的无状态性质。大多数框架为了迎合这一点,在结束页面呈现之前提供 one-shot-processing。这种方法导致很大的阻力,因为任何大的应用程序都需要长时间运行的 conversation 来满足某些用例。需要有状态上下文的应用程序的例子有很多,例如存储检查过程、产品定制、多页表单向导和很多其他基于线形交互的应用程序。虽然其中有些例子可以通过使用 URL 参数(aka RESTful URL)和隐藏字段在页面之间迁移数据,但是这样做对于开发人员来说有些繁杂。而且,如今这种做法已经过时了。因为大多数 Web 框架仍然在无状态模型下操作,所以您常常发现自己走出了这种框架,而 “开辟” 出定制解决方案。   JSF 大量依赖于 HTTP 会话,试图引入有状态上下文。实际上,当和会话作用域的 backing bean 一起使用时,JSF 组件的行为要好得多。如果不小心设计,过度使用 HTTP 会话会导致严重的内存泄漏、性能瓶颈和安全问题。此外,在多标签浏览器环境中,使用 HTTP 会话可能导致非常奇怪的行为,破坏用户神圣的 Back 按钮。值得注意的是,JSF 只是与您互作让步:它是一个有状态 UI,处理保存和恢复组件树的所有细节,但是它在保存和恢复数据方面没有提供帮助。因此,JSF 带来有状态 UI,而您则带来有状态数据。不幸的是,需要由您来负责确保它们是相符的。   在 Seam 之前,使用有状态数据的惟一方便的方式是依赖于 HTTP 会话。Seam 纠正了这个问题,它通过建立一个全新的 conversation 作用域,完善了 JSF 的状态管理。随着将 Seam 添加到 JSF 生命周期中,conversation 上下文与一个浏览器窗口(或标签页)联系在一起,这个浏览器窗口(或标签页)由随每个请求提交的一个标志来标识。conversation 作用域使用 HTTP 会话的一个单独的区段在页面之间迁移数据。记住,Seam 使用 HTTP 会话用于 conversation 持久性这一点是完全透明的。 Seam 并不是不负责任地将组件推卸到 HTTP 会话中,使其茫然地呆在那里。相反,Seam 小心地管理那个区段的会话数据的生命周期,并且当 conversation 终止时,自动清除它们。Seam 聪明地使用双射来允许以一种新的说明性方式使数据流入和流出一个 “Web conversation” 的每个页面。 Seam 的 conversation 作用域同时还克服了 HTTP 会话的限制,帮助开发人员放弃使用 HTTP 会话。 异常处理   Seam 的创立者曾说过:“在异常处理方面,JSF 非常有限”。这一点显然毫无争议。 JSF 规范完全忽视异常管理,将责任完全放在 servlet 容器上。允许 servlet 容器处理异常的问题在于,这严重限制了错误页面上显示的内容,并且禁止了事务回滚。由于错误页面是在请求分发器转发之后显示的,FacesContext 不再在作用域中,因此这时执行业务逻辑就太迟了。您的最后一线希望是使用 Servlet API,并抓住 javax.servlet.error.* 请求属性,以搜索能表明出错处的信息。   这一点上,Seam 再次扮演救世主,它提供了优雅的、说明性方式的异常处理。异常管理可以通过注释指定,或者在配置文件中指定。可以将注释 @HttpError、@Redirect 和 @ApplicationException 放在异常类的上面,表明当异常被抛出时应该采取的动作。对于那些不能修改的异常类,可以使用 XML 配置选项。Seam 中的异常管理器负责发送 HTTP 状态码、执行重定向、促使页面呈现、终止 conversation 和定制出现异常时的用户消息。由于在开始呈现响应之后,JSF 不能改变动作的过程,一些固有的限制规定了何时才能处理这些异常。通过适当使用其他 Seam 特性,例如页面动作,可以确保大多数异常情况在呈现响应之前得到解决。   Ajax remoting   由于最后发行的 JSF 规范几乎与 Ajax 重合,JSF 框架在异步 JavaScript 和局部页面呈现(partial page rendering)方面帮助不大。在某些时候,甚至这两种类型的编程甚至不一致。最终的解决办法是建议使用 JSF PhaseListener 或组件 Renderer 来处理局部页面更新。即使如此,这一点仍然很明显:JSF 使得 Ajax 比过去更难于采用。有些项目,例如 ICEfaces,甚至用一个更好的、专为页面-服务器通信设计的技术(即 direct-to-DOM rendering)来替代 JSF 生命周期。   Seam 为 JavaScript remoting(常常记在 Ajax 名下的一种技术)提供了一种独特的方式,该方式与 Direct Web Remoting (DWR) 库的方式大致相似。Seam 通过允许 JavaScript 直接调用服务器组件上的方法,将客户机与服务器连在一起。Seam remoting 比 DWR 更强大,因为它可以访问丰富的上下文组件模型,而不仅仅是一个孤立的端点。这种交互构建在 JSF 的事件驱动设计的基础上,所以它可以更严格地遵从 Swing 范例。最妙的是,提供这个功能的同时并没有增加开发方面的负担。只需在组件的方法上加一个简单的注释 @WebRemote,JavaScript 就可以访问该方法。当服务器组件的状态被修改之后,Ajax4JSF 组件库就可以处理局部呈现。简言之:Seam remoting 库使 JSF 可以实现它的创立者一向期望的交互设计。   结束语   根据您目前在 无缝集成 JSF 系列 中了解到的内容,可以毫不牵强地说在使用 JSF 的开发中不使用 Seam 是反常的。作为进一步的证明,只需看看 JSR 299, Web Beans 的投票结果(见 参考资料)。显然,在不久的将来,Seam 会成为一个官方规范,Java EE 栈最终将提供 “显著简化的基于 Web 的应用程序编程模型”。这对 JSF 开发人员和 Seam 来说是一个好消息。但是,即使没有声明要成为一个 Java 标准,Seam 也是 JSF 的一个有价值的补充。   Seam 只需很少的设置就可以开始用于 JSF —— 而正是这一点小小的付出,就能解决 JSF 开发中的一些最麻烦的难题。回报胜于付出 —— 这里讨论的 Seam 的优点还只是一个开始。 相关资源:敏捷开发V1.0.pptx
转载请注明原文地址: https://www.6miu.com/read-5015218.html

最新回复(0)