1. 什么是 SSE? (30秒)
SSE (Server-Sent Events) 是一種允許服務器通過 HTTP 連接主動向客戶端發送實時更新的技術。
特點:基于 HTTP,使用簡單,單向通信(服務器 -> 客戶端),自動重連。
對比 WebSocket:WebSocket 是雙向的,更復雜;SSE 是單向的,更輕量,適用于通知、日志流、實時數據更新等場景。
2. 核心依賴與配置 (30秒)
Spring Boot 從 2.2.x 版本開始提供了對 SSE 的專用支持,主要包含在?spring-boot-starter-web
?中,無需引入額外依賴。
確保你的?pom.xml
?中有:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
3. 三步編寫代碼 (3分鐘)
第一步:創建控制器 (Controller)
創建一個?@RestController
,并定義一個方法來產生 SSE 流。
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@RestController
public class SseController {// 用于保存所有連接的 SseEmitter,可以根據用戶ID等關鍵字進行存儲private static final Map<String, SseEmitter> EMITTER_MAP = new ConcurrentHashMap<>();/*** 用于客戶端連接 SSE* @param clientId 客戶端標識,用于區分不同客戶端* @return SseEmitter*/@GetMapping(path = "/sse/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter connect(@RequestParam String clientId) {// 設置超時時間,0表示永不超時。可以根據需要設置,例如 30_000L (30秒)SseEmitter emitter = new SseEmitter(0L);// 注冊回調函數,當連接完成或出錯時,從Map中移除這個Emitteremitter.onCompletion(() -> EMITTER_MAP.remove(clientId));emitter.onError((e) -> EMITTER_MAP.remove(clientId));emitter.onTimeout(() -> EMITTER_MAP.remove(clientId));// 將新的 emitter 存入 MapEMITTER_MAP.put(clientId, emitter);// 可選:發送一個初始連接成功的事件try {emitter.send(SseEmitter.event().name("INIT") // 事件名稱,可選.data("連接成功 for: " + clientId) // 事件數據.id("1") // 事件ID,可選,用于重連.reconnectTime(5000)); // 重連時間,可選} catch (IOException e) {e.printStackTrace();}return emitter;}
}
第二步:創建發送消息的方法
在同一個 Controller 中,添加一個 API 來模擬向特定客戶端發送消息。
/*** 向指定客戶端發送消息*/@GetMapping("/sse/send")public String sendMessage(@RequestParam String clientId, @RequestParam String message) {SseEmitter emitter = EMITTER_MAP.get(clientId);if (emitter != null) {try {// 構建并發送事件emitter.send(SseEmitter.event().name("MESSAGE") // 事件類型.data(message) // 事件數據.id("msg-id-" + System.currentTimeMillis())); // ID} catch (IOException e) {// 發送失敗,移除 emitterEMITTER_MAP.remove(clientId);return "發送失敗,客戶端可能已斷開";}return "發送成功 to: " + clientId;}return "客戶端不存在";}
第三步:編寫前端頁面進行測試 (1分鐘)
在?src/main/resources/static
?目錄下創建一個?sse-demo.html
?文件。
<!DOCTYPE html>
<html>
<head><title>SSE Demo</title>
</head>
<body><h1>SSE 客戶端測試</h1><label for="clientId">客戶端ID: </label><input type="text" id="clientId" value="test-client-1"><button onclick="connectSSE()">連接SSE</button><button onclick="closeSSE()">斷開連接</button><hr><label for="message">要發送的消息: </label><input type="text" id="message" value="Hello SSE!"><button onclick="sendMessage()">發送消息</button><hr><h3>收到的事件:</h3><div id="messages"></div><script>let eventSource;function connectSSE() {const clientId = document.getElementById('clientId').value;// 斷開現有連接if (eventSource) {eventSource.close();}// 建立新的 SSE 連接eventSource = new EventSource(`/sse/connect?clientId=${clientId}`);// 監聽通用消息(沒有指定 event name 的消息)eventSource.onmessage = function (event) {appendMessage(`[message]: ${event.data}`);};// 監聽特定名稱的事件 (例如:MESSAGE)eventSource.addEventListener("MESSAGE", function (event) {appendMessage(`[MESSAGE]: ${event.data}`);});// 監聽特定名稱的事件 (例如:INIT)eventSource.addEventListener("INIT", function (event) {appendMessage(`[INIT]: ${event.data}`);});eventSource.onerror = function (err) {console.error("SSE error:", err);appendMessage('[錯誤] 連接出錯');};}function closeSSE() {if (eventSource) {eventSource.close();appendMessage('[信息] 連接已關閉');eventSource = null;}}function sendMessage() {const clientId = document.getElementById('clientId').value;const message = document.getElementById('message').value;fetch(`/sse/send?clientId=${clientId}&message=${encodeURIComponent(message)}`).then(response => response.text()).then(data => console.log(data));}function appendMessage(text) {const messageDiv = document.getElementById('messages');const p = document.createElement('p');p.textContent = `${new Date().toLocaleTimeString()}: ${text}`;messageDiv.appendChild(p);}</script>
</body>
</html>
4. 運行與測試 (1分鐘)
啟動應用:運行你的 Spring Boot 應用。
打開頁面:訪問?
http://localhost:8080/sse-demo.html
。進行測試:
輸入一個客戶端 ID(如?
user1
),點擊?“連接SSE”。前端會收到?[INIT]
?事件。在另一個瀏覽器標簽頁或使用 Postman 訪問 :??
http://localhost:8080/sse/send?clientId=user1&message=你好!
。觀察第一個標簽頁,會立即收到?
[MESSAGE]: 你好!
?的消息。
總結
核心對象:
SseEmitter
關鍵注解:
@GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
流程:
客戶端連接?
/sse/connect
,服務端創建并保存?SseEmitter
。服務端通過?
emitter.send()
?主動推送消息。客戶端通過?
EventSource
?API 監聽和處理消息。連接結束時,服務端需要清理?
SseEmitter
(通過回調函數)。
現在你已經掌握了 Spring Boot 整合 SSE 的基本方法!在實際項目中,你可能需要將其與業務邏輯、身份認證(如 JWT)以及更強大的連接管理(如使用數據庫或 Redis 存儲 emitter)相結合。