JSF2.0:按钮rendered条件不满足时提交如何抛出异常
解决方案:JSF2.0并发操作下的乐观锁与无刷新异常处理
问题根源
你遇到的是会话Bean并发修改导致的视图状态不一致问题:
- 两个标签页共享同一个
@SessionScoped的CountBean,标签1修改count后,标签2的视图仍保留旧状态。 - 当标签2提交
decrease请求时,服务器端检查rendered="#{countBean.count > 0}"条件已不满足(count已被标签1减到0),JSF会忽略该按钮的action执行,直接刷新页面,导致操作静默失败。
实现步骤
1. 给CountBean添加乐观锁控制
修正Bean的Java代码结构,加入版本号字段用于并发冲突检测:
import javax.faces.bean.SessionScoped; import javax.inject.Named; import java.io.Serializable; @Named @SessionScoped public class CountBean implements Serializable { private int count = 1; private int version = 0; // 乐观锁版本标识 private int submittedVersion; // 接收前端提交的版本号 public void increase() { version++; count++; } public void decrease() { // 校验版本号,检测并发修改 if (this.version != submittedVersion) { throw new RuntimeException("数据已被其他操作修改,请重新尝试"); } version++; count--; } // Getter & Setter public int getCount() { return count; } public int getVersion() { return version; } public void setSubmittedVersion(int submittedVersion) { this.submittedVersion = submittedVersion; } }
2. 改造页面为AJAX提交,避免整页刷新
修改count.xhtml,使用f:ajax实现局部提交,传递版本号并处理错误:
<!-- 修正rendered逻辑:原>100初始无法显示,改为<100合理展示increase按钮 --> <ui:fragment rendered="#{countBean.count < 100}"> <h:commandButton id="increase-button" value="increase" action="#{countBean.increase}"> <f:ajax execute="@form" render="@form" onerror="handleOperationError(xhr)" /> <f:param name="countBean.submittedVersion" value="#{countBean.version}" /> </h:commandButton> </ui:fragment> <ui:fragment rendered="#{countBean.count > 0}"> <h:commandButton id="decrease-button" value="decrease" action="#{countBean.decrease}"> <f:ajax execute="@form" render="@form" onerror="handleOperationError(xhr)" /> <f:param name="countBean.submittedVersion" value="#{countBean.version}" /> </h:commandButton> </ui:fragment> <script> function handleOperationError(xhr) { // 弹出异常信息,或渲染到页面指定区域 alert("操作失败:" + xhr.responseText); } </script>
注意:修正了按钮id重复问题(原两个按钮id都是
submit-button,违反JSF页面id唯一性要求)。
3. 自定义异常处理器捕获并发异常
创建自定义异常处理器,捕获乐观锁异常并返回错误信息,避免页面刷新:
CustomExceptionHandlerFactory.java
import javax.faces.context.ExceptionHandler; import javax.faces.context.ExceptionHandlerFactory; public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory { private final ExceptionHandlerFactory parent; public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) { this.parent = parent; } @Override public ExceptionHandler getExceptionHandler() { return new CustomExceptionHandler(parent.getExceptionHandler()); } }
CustomExceptionHandler.java
import javax.faces.context.ExceptionHandler; import javax.faces.context.ExceptionHandlerWrapper; import javax.faces.context.FacesContext; import javax.faces.event.ExceptionQueuedEvent; import java.io.IOException; import java.util.Iterator; public class CustomExceptionHandler extends ExceptionHandlerWrapper { private final ExceptionHandler wrapped; public CustomExceptionHandler(ExceptionHandler wrapped) { this.wrapped = wrapped; } @Override public ExceptionHandler getWrapped() { return wrapped; } @Override public void handle() { Iterator<ExceptionQueuedEvent> events = getUnhandledExceptionQueuedEvents().iterator(); while (events.hasNext()) { ExceptionQueuedEvent event = events.next(); Throwable throwable = event.getContext().getException(); // 捕获自定义并发异常 if (throwable.getMessage().contains("数据已被其他操作修改")) { FacesContext context = FacesContext.getCurrentInstance(); try { context.getExternalContext().setResponseStatus(409); context.getExternalContext().getResponseOutputWriter().write(throwable.getMessage()); context.responseComplete(); // 终止JSF生命周期,避免页面刷新 } catch (IOException e) { e.printStackTrace(); } events.remove(); } } getWrapped().handle(); } }
在faces-config.xml中注册异常处理器
<factory> <exception-handler-factory>com.yourpackage.CustomExceptionHandlerFactory</exception-handler-factory> </factory>
效果验证
当两个标签页并发操作时:
- 标签1执行
decrease后,count变为0,版本号更新。 - 标签2提交
decrease请求时,版本号校验失败,抛出异常。 - 自定义异常处理器捕获异常,返回错误信息,AJAX回调弹出提示,页面不会刷新。
内容的提问来源于stack exchange,提问作者Nguyễn Hữu Thanh
相关产品推荐
相关产品推荐

