JAX-RS中是否应复用SseEventSink或保持其开启?
JAX-RS SSE端点最佳实践解答
关于EventSink共享的问题
- 绝对不能跨客户端共享EventSink实例:每个
SseEventSink都绑定到单个客户端的HTTP长连接,生命周期和客户端连接完全绑定。如果多个线程(不同客户端)共用同一个EventSink,会导致消息发送到错误的客户端,同时SseEventSink本身并非线程安全,多线程操作会引发并发问题。 - 你类中注入的
@Context SseEventSink eventSink是无效的,因为SseEventSink是请求作用域的,只能在请求方法中通过参数注入,类级别的注入无法获取到有效的实例。
关于try-with-resources的处理
- 如果需要保持长连接持续推送消息,必须移除
try (SseEventSink sink = eventSink)代码块。try-with-resources会在代码块执行完毕后自动关闭EventSink,也就是关闭客户端的HTTP连接,这意味着客户端只能收到一次消息就断开了,不符合SSE长连接推送的需求。 - 正确的做法是保留
eventSink的引用(比如存入一个线程安全的集合),在需要推送消息时调用它的send方法,直到客户端主动断开连接或者服务端决定关闭连接时,再手动调用eventSink.close()。
关于EventSink创建的性能问题
SseEventSink属于轻量级对象,它只是对底层HTTP输出流的包装,并非重量级资源。每次客户端请求创建一个实例是SSE的标准设计模式,不存在性能问题,无需担心频繁创建带来的开销。- 你需要维护一个
SseEventSink的线程安全集合(比如CopyOnWriteArrayList),在客户端连接时将eventSink加入集合,连接关闭时(通过eventSink的onClose回调)从集合中移除,这样就能实现向所有在线客户端推送消息。
优化后的示例代码
@Path("/") @ApplicationScoped public class RestRepo { @Context private Sse sse; // 用线程安全的集合维护所有活跃的EventSink private final List<SseEventSink> activeSinks = new CopyOnWriteArrayList<>(); @GET @Path("/sse") @Produces(MediaType.SERVER_SENT_EVENTS) public void registerClient(final @Context SseEventSink eventSink) { // 将新的EventSink加入集合 activeSinks.add(eventSink); // 注册连接关闭回调,从集合中移除失效的sink eventSink.onClose(() -> activeSinks.remove(eventSink)); // 发送初始连接确认消息 eventSink.send(sse.newEvent("已连接到SSE服务")); } // 向所有在线客户端广播消息的方法 public void broadcastMessage(String message) { SseEvent event = sse.newEventBuilder() .data(message) .build(); for (SseEventSink sink : activeSinks) { // 异步发送消息,避免阻塞主线程 sink.send(event, asyncResult -> { if (asyncResult.failed()) { // 发送失败,移除失效的sink activeSinks.remove(sink); } }); } } }
内容的提问来源于stack exchange,提问作者nimo23
相关产品推荐
相关产品推荐

