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

如何通过EJB 3.2定时器结合WebSocket向JSF 2.2客户端定时发消息

我来一步步帮你搞定这个需求——用EJB 3.2的定时服务每10秒触发一次,然后通过WebSocket从服务器给JSF 2.2客户端推送消息。整个流程拆成几个核心部分,咱们逐个说清楚:

1. 项目基础准备

首先你需要一个支持Java EE 7的服务器(比如WildFly 10+、GlassFish 4+),项目不用额外加第三方依赖,Java EE 7 API已经包含了EJB、WebSocket和JSF的所有必要组件,直接用服务器自带的即可。

2. 实现WebSocket端点

先写一个WebSocket服务端端点,用来维护客户端连接,并且提供广播消息的方法供定时器调用:

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/timerPush")
public class TimerWebSocketEndpoint {

    // 用线程安全的集合保存所有活跃的WebSocket会话(避免并发问题)
    private static final Set<Session> activeSessions = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onClientConnect(Session session) {
        activeSessions.add(session);
        System.out.println("新客户端上线: " + session.getId());
    }

    @OnClose
    public void onClientDisconnect(Session session) {
        activeSessions.remove(session);
        System.out.println("客户端下线: " + session.getId());
    }

    @OnMessage
    public void handleClientMessage(String message, Session session) throws IOException {
        // 可选:处理客户端发来的消息,这里简单返回确认
        session.getBasicRemote().sendText("收到你的消息: " + message);
    }

    // 核心方法:供EJB定时器调用,向所有在线客户端广播消息
    public static void broadcastToAllClients(String message) {
        for (Session session : activeSessions) {
            if (session.isOpen()) {
                try {
                    session.getBasicRemote().sendText(message);
                } catch (IOException e) {
                    e.printStackTrace();
                    // 单个客户端发送失败不影响其他客户端
                }
            }
        }
    }
}

这里的关键细节:

  • @ServerEndpoint("/timerPush") 定义了WebSocket的访问路径,客户端要通过这个路径连接
  • CopyOnWriteArraySet 是线程安全的集合,适合多线程环境下(EJB定时器、WebSocket连接)操作会话集合
3. 实现EJB 3.2定时服务

写一个Singleton EJB,用@Schedule注解配置每10秒执行一次任务,然后调用WebSocket的广播方法:

import javax.ejb.Singleton;
import javax.ejb.Schedule;
import java.time.LocalDateTime;

@Singleton
public class TimerServiceBean {

    // 每10秒执行一次,表达式规则:秒 分 时 日 月 周 年(年可选)
    // persistent=false 表示服务器重启后定时器不恢复,适合测试场景
    @Schedule(second = "*/10", minute = "*", hour = "*", persistent = false)
    public void sendTimedMessage() {
        String pushMessage = "服务器定时推送 - 当前时间: " + LocalDateTime.now();
        // 调用WebSocket端点的广播方法
        TimerWebSocketEndpoint.broadcastToAllClients(pushMessage);
        System.out.println("已推送消息: " + pushMessage);
    }
}

为什么用@Singleton?因为我们只需要一个定时器实例,避免多个EJB实例重复创建定时任务。

4. 实现JSF 2.2客户端页面

写一个JSF页面,用JavaScript连接WebSocket,接收服务器推送的消息并展示在页面上:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
    <title>EJB定时器WebSocket推送演示</title>
    <script type="text/javascript">
        let webSocketConnection;

        // 页面加载时初始化WebSocket连接
        function initWebSocket() {
            // 注意:这里的路径要和ServerEndpoint的注解一致,还要加上你的应用上下文
            // 假设你的应用部署名为timerDemo,那么完整路径是 ws://localhost:8080/timerDemo/timerPush
            const wsUrl = `ws://${window.location.host}/timerDemo/timerPush`;
            webSocketConnection = new WebSocket(wsUrl);

            // 接收服务器推送的消息
            webSocketConnection.onmessage = function(event) {
                const messageArea = document.getElementById("messageDisplay");
                messageArea.innerHTML += `${event.data}<br/>`;
                // 自动滚动到最新消息
                messageArea.scrollTop = messageArea.scrollHeight;
            };

            webSocketConnection.onclose = function() {
                console.log("WebSocket连接已关闭");
            };

            webSocketConnection.onerror = function(error) {
                console.error("WebSocket出错: ", error);
            };
        }

        // 页面关闭前断开WebSocket连接
        window.onbeforeunload = function() {
            if (webSocketConnection) {
                webSocketConnection.close();
            }
        };
    </script>
</h:head>
<h:body onload="initWebSocket()">
    <h1>EJB定时器WebSocket推送演示</h1>
    <div id="messageDisplay" style="width: 700px; height: 450px; border: 1px solid #ddd; padding: 15px; overflow-y: auto;"></div>
</h:body>
</html>

注意这里的wsUrl要替换成你自己的应用上下文,比如你的应用部署在服务器上的路径是/myTimerApp,那就要改成ws://${window.location.host}/myTimerApp/timerPush。

5. 部署与测试

把项目打包成war包,部署到Java EE 7服务器上,然后访问这个JSF页面(比如http://localhost:8080/timerDemo/index.xhtml),你就能看到页面每10秒自动刷新一条来自服务器的时间消息了。

关键注意事项
  • 线程安全:WebSocket的会话集合必须用线程安全的实现,否则多线程环境下会出现并发问题
  • EJB定时器的persistent属性:如果设置为true,服务器重启后会继续之前的定时任务,测试阶段建议设为false
  • 异常处理:广播消息时要捕获单个客户端的发送异常,避免一个客户端出错导致整个广播失败

内容的提问来源于stack exchange,提问作者Kyaw Soe Han

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:58:13