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

基于JavaEE8(Liberty),JSF viewScope下共享记录删除后如何更新其他用户视图?

我来给你几个适配Java EE 8 WebProfile(Liberty 18.0.0.4)的解决方案,既能保留viewScope的优势,又能实现共享记录变更后的跨用户视图更新:


方案1:CDI事件+WebSocket广播(实时性最优)

这是最推荐的方案,能实现实时通知,不需要轮询,完全贴合Java EE生态:

  1. 定义CDI事件载体
    创建一个事件类,用来传递被删除的共享记录信息(如果共享记录是按组/项目划分的,记得带上分组标识,避免给无关用户发通知):
public class SharedRecordDeletedEvent implements Serializable {
    private final Long recordId;
    private final String sharedGroupId; // 比如用户组、项目ID,无分组可移除

    public SharedRecordDeletedEvent(Long recordId, String sharedGroupId) {
        this.recordId = recordId;
        this.sharedGroupId = sharedGroupId;
    }

    // 生成getter方法
}
  1. 删除操作时触发事件
    在处理删除逻辑的Service中,注入CDI事件并触发:
@Stateless
public class RecordService {
    @Inject
    private Event<SharedRecordDeletedEvent> deleteEvent;

    public void deleteSharedRecord(Long recordId, String sharedGroupId) {
        // 执行MySQL删除语句
        entityManager.remove(findRecordById(recordId));
        
        // 广播删除事件
        deleteEvent.fire(new SharedRecordDeletedEvent(recordId, sharedGroupId));
    }
}
  1. WebSocket端点监听事件并推送消息
    创建WebSocket端点,监听CDI事件,然后给对应共享组的所有在线客户端推送删除通知:
@ServerEndpoint("/sharedRecords/{sharedGroupId}")
public class SharedRecordWebSocket {
    // 用并发容器存储每个共享组的在线会话
    private static final Map<String, Set<Session>> groupSessions = new ConcurrentHashMap<>();

    @OnOpen
    public void onSessionOpen(Session session, @PathParam("sharedGroupId") String groupId) {
        groupSessions.computeIfAbsent(groupId, k -> ConcurrentHashMap.newKeySet()).add(session);
    }

    @OnClose
    public void onSessionClose(Session session, @PathParam("sharedGroupId") String groupId) {
        Set<Session> sessions = groupSessions.get(groupId);
        if (sessions != null) {
            sessions.remove(session);
            if (sessions.isEmpty()) groupSessions.remove(groupId);
        }
    }

    // 监听CDI删除事件,推送消息给对应组的客户端
    public void onRecordDeleted(@Observes SharedRecordDeletedEvent event) {
        Set<Session> sessions = groupSessions.get(event.getSharedGroupId());
        if (sessions == null || sessions.isEmpty()) return;

        for (Session session : sessions) {
            try {
                session.getBasicRemote().sendText("DELETED:" + event.getRecordId());
            } catch (IOException e) {
                // 移除失效会话
                sessions.remove(session);
            }
        }
    }
}
  1. 前端连接WebSocket并处理通知
    在页面加载时,根据当前共享组连接WebSocket,收到删除消息后更新视图:
const sharedGroupId = "${sampleBean.sharedGroupId}"; // 从viewScope Bean获取当前组ID
const ws = new WebSocket(`ws://${window.location.host}/your-app-context/sharedRecords/${sharedGroupId}`);

ws.onmessage = function(event) {
    const [action, recordId] = event.data.split(":");
    if (action === "DELETED") {
        // 方式1:调用JSF ajax刷新列表
        document.getElementById('refreshBtn').click();
        // 方式2:直接从DOM移除对应记录行
        document.getElementById(`record-${recordId}`)?.remove();
    }
};

ws.onclose = function() {
    // 可选:断开后自动重连
    setTimeout(() => window.location.reload(), 3000);
};
  1. Liberty配置
    确保server.xml启用了必要特性(WebProfile 8.0已包含这些,若未自动启用可手动添加):
<featureManager>
    <feature>webProfile-8.0</feature>
    <feature>cdi-2.0</feature>
    <feature>websocket-1.1</feature>
</featureManager>

方案2:CDI事件+AJAX轮询(兼容性更广)

如果无法使用WebSocket(比如浏览器限制),可以用轮询结合轻量的全局状态跟踪:

  1. 定义CDI事件和触发逻辑
    和方案1的步骤1、2完全一致。

  2. 全局跟踪删除记录
    创建一个ApplicationScoped的Bean,仅存储最近删除的共享记录ID(不会存储所有数据,避免内存问题):

@ApplicationScoped
public class SharedRecordChangeTracker implements Serializable {
    private final ConcurrentMap<String, Set<Long>> deletedRecordIds = new ConcurrentHashMap<>();

    // 监听CDI事件,更新删除记录集合
    public void trackDeletedRecord(@Observes SharedRecordDeletedEvent event) {
        deletedRecordIds.computeIfAbsent(event.getSharedGroupId(), k -> ConcurrentHashMap.newKeySet()).add(event.getRecordId());
    }

    // 供viewScope Bean获取并清空当前组的删除记录
    public Set<Long> fetchAndClearDeletedRecords(String groupId) {
        return deletedRecordIds.remove(groupId);
    }
}
  1. viewScope Bean添加刷新方法
    在你的sampleBean中,添加方法检查是否有新的删除记录并更新本地数据:
@ViewScoped
@Named("sampleBean")
public class SampleBean implements Serializable {
    @Inject
    private SharedRecordChangeTracker changeTracker;
    @Inject
    private RecordService recordService;

    private List<Record> records;
    private String sharedGroupId;

    @PostConstruct
    public void init() {
        loadRecords();
    }

    private void loadRecords() {
        records = recordService.getSharedRecords(sharedGroupId);
    }

    // 供AJAX调用的刷新方法
    public void refreshIfNeeded() {
        Set<Long> deletedIds = changeTracker.fetchAndClearDeletedRecords(sharedGroupId);
        if (deletedIds != null && !deletedIds.isEmpty()) {
            // 方式1:从本地列表移除删除项
            records.removeIf(record -> deletedIds.contains(record.getId()));
            // 方式2:重新加载整个列表(数据量小时推荐)
            // loadRecords();
        }
    }
}
  1. 前端定时轮询
    用JS定时触发AJAX请求,更新视图:
<h:form id="refreshForm">
    <h:commandButton id="refreshBtn" action="#{sampleBean.refreshIfNeeded}" style="display:none">
        <f:ajax render="recordList" />
    </h:commandButton>
</h:form>

<script>
    // 每5秒轮询一次(可根据业务调整间隔)
    setInterval(() => {
        document.getElementById('refreshForm:refreshBtn').click();
    }, 5000);
</script>

关键注意事项

  • 范围控制:一定要用共享组/项目ID来限定通知范围,避免给无关用户发送更新。
  • viewScope生命周期:viewScope仅在当前视图会话中有效,前端必须绑定到当前视图的上下文(比如共享组ID)。
  • Liberty兼容性:你的Liberty 18.0.0.4完全支持Java EE 8 WebProfile的所有特性,上述方案均可直接运行。

内容的提问来源于stack exchange,提问作者mah454

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:11:51