You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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. 标签1执行decrease后,count变为0,版本号更新。
  2. 标签2提交decrease请求时,版本号校验失败,抛出异常。
  3. 自定义异常处理器捕获异常,返回错误信息,AJAX回调弹出提示,页面不会刷新。

内容的提问来源于stack exchange,提问作者Nguyễn Hữu Thanh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 15:05:26