在响应被渲染之前如何处理多次提交?

10
据测试报告,如果响应渲染不够快,会偶尔出现重复按下按钮的情况,导致多次调用后端代码,这是我们不想看到的。
该应用程序使用Java EE 6 Web Profile,在Glassfish 3.1.1中使用JSF 2.0。
我在思考如何正确处理这个问题,并想到了几种方案:
  • 提交时应使用JavaScript禁用所有按钮,同时等待响应被渲染。
  • 在会话范围内设置一个标志表示已经处于活动状态,因此敏感代码将被跳过,直接继续重新呈现以前的提交的响应。
  • 使用同步块延迟处理,直到上一个请求完成。然后应检测到它已经被处理并跳过。
  • 使用类似转换的“新”作用域来处理检测?
我最初的直觉是将敏感代码块设置为原子操作,但这会导致正确响应的渲染问题。
我应该如何处理这个问题?

你尝试过类似jQuery blockui这样的东西吗?顺便说一句,在集群上同步块是行不通的。 - Adrian Mitev
还没有尝试过任何东西。这是处于“好吧,我该怎么做?”阶段。 - Thorbjørn Ravn Andersen
2个回答

10
提交时应使用javascript禁用所有按钮,直到响应被渲染。

这是最容易以通用方式实现的。如果您只使用 <f:ajax>,您可以使用 jsf.ajax.addOnEvent() 以通用方式执行该作业。另一种JavaScript方法是创建一种“加载”覆盖层,它会阻止UI,使最终用户无法再与底层页面交互。这基本上是一个绝对定位的隐藏 <div>,其跨越整个视口并具有一些 opacity(透明度)。您可以在提交时显示它,并在呈现时隐藏它。这种技术的关键词是 "modal dialog"。面向UI的JSF组件库已经至少拥有这样一个组件。例如,PrimeFaces 中有一个 <p:dialog modal="true"><p:ajaxStatus> 中,或者 <p:blockUI>

唯一的缺点是,如果客户端禁用了JS或者不使用JS,那么它将无法工作,因此它不能防止HTTP客户端进行重复提交。
在Session作用域中设置一个标志,指示其已经处于活动状态,因此敏感代码会被跳过,直接进行上一次提交的响应重新渲染。这更为人所知为"同步令牌模式",已经通过spec issue 559 请求JSF实现,目前已定位到2.2版本,但似乎没有任何活动。检测和阻止部分在技术上很容易实现,但如果您希望最终用户检索由初始请求生成的响应,则同步响应处理部分不易实现。异步响应处理很容易:只需不指定要更新的组件,即清空由PartialViewContext#getRenderIds()返回的集合。毕竟,这比使用JS禁用按钮或阻止UI更为强大。
据我所知,Seam 2是唯一提供可重用JSF组件<s:token>的人。然而,我必须承认这对于一个新的OmniFaces组件来说是一个有趣的想法。也许我会亲自研究一下它。
一个同步块可以延迟处理,直到前一个请求完成。然后应该检测到它已经被处理过并跳过。一般情况下,这不容易实现,需要更改所有操作方法以检查作业是否已经完成。如果Web应用程序在多个服务器上运行,则也无法正常工作。同步令牌更容易实现,因为它将在调用操作方法之前执行。同步令牌也较便宜,因为您不会在队列中得到多个请求,这只会消耗线程/资源。

使用类似于转换的“新”范围来处理检测?

通过玩弄托管 bean 范围无法解决此问题。托管 bean 范围具有不同的目的:bean 实例的生命周期。


除了“同步块”之外,您还可以查看此过滤器 - http://onjava.com/onjava/2004/03/24/examples/RequestControlFilter.java - Adrian Mitev
非常感谢您提供如此详细的答案。您是否有其他我没有想到的建议? - Thorbjørn Ravn Andersen
不客气。除了使用JS禁用/阻止用户交互或使用同步令牌模式外,实际上没有其他方法。 - BalusC
顺便说一句,我已经为OmniFaces查看过它了,在同步请求上很容易使其工作,但在ajax请求上失败了。请参见http://code.google.com/p/omnifaces/issues/detail?id=76的评论3。 - BalusC

3

正如Adrian所提到的,我也建议使用BlockUI。 Primefaces有一个BlockUI组件。当您通过ajax提交表单时,可以在请求期间使用叠加层。请参见Primefaces的Ajax状态以获取示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接