本文目录导读:

- 核心原理对比
- 案例一:基于 SSE (Server-Sent Events) 推送通知
- 案例二:基于 WebSocket (Spring Boot + WebSocket)
- 案例三:基于消息队列 (MQ) 的推送(高并发场景)
- 总结:如何选择?
Java 站内推送的实现方案有很多种,核心目标是服务端主动向浏览器/客户端发送消息,而不是客户端轮询。
以下是几种主流的 Java 实现方案,从简单到复杂,并附上对应的案例代码和适用场景。
核心原理对比
| 方案 | 技术栈 | 延迟 | 复杂度 | 浏览器兼容性 | 适用场景 |
|---|---|---|---|---|---|
| 短轮询 | HTTP (Ajax) | 高 (几秒) | 最低 | 全兼容 | 实时性要求低,技术栈老旧 |
| 长轮询 | HTTP (Comet) | 中 | 低 | 全兼容 | 实时性要求一般,无法使用WebSocket |
| SSE (推荐) | HTTP (Servlet) | 低 (<1s) | 低 | 现代浏览器 (不支持IE) | 服务端单向推送 (如通知、股票) |
| WebSocket | TCP (JSR 356) | 极低 | 高 | 现代浏览器 | 双向通信 (如聊天、协同编辑) |
基于 SSE (Server-Sent Events) 推送通知
这是目前 最推荐 的站内推送方式,服务端可以持续向客户端发送事件流。
特点:单向(服务端→客户端)、基于HTTP、轻量级、自动重连。
后端 Java 代码 (Spring Boot)
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;
import java.time.Duration;
import java.time.LocalTime;
@RestController
public class NotificationController {
// 1. 创建一个发布者(用于持续发送消息)
private final Sinks.Many<String> sink = Sinks.many().multicast().onBackpressureBuffer();
@GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> subscribe() {
// 2. 返回一个Flux流,Spring会将其转换为SSE格式
return sink.asFlux()
.doOnCancel(() -> System.out.println("客户端断开连接"));
}
// 3. 模拟外部系统触发推送(比如管理员发通知)
public void sendNotification(String message) {
System.out.println("推送消息: " + message);
sink.tryEmitNext("data: " + message);
}
// 4. 模拟周期性心跳
@GetMapping("/tick")
public Flux<String> tick() {
return Flux.interval(Duration.ofSeconds(5))
.map(i -> "data: 心跳 " + LocalTime.now().toString());
}
}
前端 HTML/JS 代码
<!DOCTYPE html>
<html>
<head>站内推送 SSE</title>
</head>
<body>
<h1>站内通知</h1>
<div id="messages"></div>
<script>
// 1. 创建EventSource连接到后端
const eventSource = new EventSource('/subscribe');
// 2. 监听消息事件
eventSource.onmessage = function(event) {
console.log('收到消息:', event.data);
const div = document.getElementById('messages');
div.innerHTML += `<p>${event.data}</p>`;
};
// 3. 监听错误/断开
eventSource.onerror = function() {
console.error('连接断开,浏览器会自动重连...');
};
// 4. 如果需要关闭连接
// eventSource.close();
</script>
</body>
</html>
优点:
- 自动重连(断线后浏览器自动尝试重连)。
- 兼容性好(现代浏览器都支持)。
- 代码简单,Spring Boot 原生支持。
基于 WebSocket (Spring Boot + WebSocket)
适用于双向通信场景(如聊天、协同)。
WebSocket 配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocket 服务器端点
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint("/websocket/{userId}")
public class WebSocketServer {
// 存储所有连接的客户端(用于广播)
private static final CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<>();
private Session session;
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
sessions.add(session);
System.out.println("用户 " + userId + " 已连接");
// 可以保存 userId -> session 映射
}
@OnClose
public void onClose() {
sessions.remove(session);
System.out.println("连接关闭");
}
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("收到消息: " + message);
// 处理业务逻辑...
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
// 广播给所有客户端
public static void broadcast(String message) {
for (Session s : sessions) {
try {
s.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
前端代码
// 连接 WebSocket
const ws = new WebSocket('ws://localhost:8080/websocket/user123');
ws.onopen = function() {
console.log('连接成功');
ws.send('你好服务器');
};
ws.onmessage = function(event) {
console.log('收到服务器消息:', event.data);
// 更新页面...
};
ws.onclose = function() {
console.log('连接关闭');
};
基于消息队列 (MQ) 的推送(高并发场景)
如果系统已经有了 RabbitMQ 或 Kafka,可以利用 MQ 广播 + WebSocket/SSE 的方式实现推送,这样做的优势是解耦和异步。
架构流程:
- 业务系统发送消息到 MQ。
- 后端服务订阅 MQ 消息。
- 后端通过 WebSocket 或 SSE 推送给对应的前端用户。
// 消费MQ消息
@RabbitListener(queues = "notification.queue")
public void handleNotification(NotificationMessage message) {
// 根据userId找到对应的WebSocket Session (需要提前存储)
WebSocketSession session = sessionMap.get(message.getUserId());
if (session != null && session.isOpen()) {
session.sendMessage(new TextMessage(message.getContent()));
}
}
如何选择?
| 需求场景 | 推荐方案 | 理由 |
|---|---|---|
| 频率高、单向通知(如系统通知、更新公告) | SSE | 简单、自动重连、HTTP友好 |
| 聊天、协同编辑、需要双向发送 | WebSocket | 全双工、低延迟 |
| 简单、老旧系统、兼容IE | 长轮询 (Long Polling) | 兼容性最好 |
| 高并发、微服务架构、需要解耦 | WebSocket + MQ | 异步、削峰填谷 |
| 很少更新、精确数据(如订单状态) | 接口 + 前端轮询 | 开发最快,后台只需写一个Controller |
最推荐的组合:SSE (服务端) + EventSource (前端) 对于绝大多数 Java 站内通知需求来说,是最快、最稳、代码最少的方案。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播