如何通过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
相关产品推荐
相关产品推荐

