使用Spring SseEmitter實(shí)現(xiàn)服務(wù)端推送的示例代碼
SseEmitter 是 Spring MVC 4.2+ 引入的一個(gè)類,專門用于實(shí)現(xiàn) Server-Sent Events (SSE)。簡(jiǎn)單來說,SSE 允許服務(wù)器在建立連接后,主動(dòng)向客戶端推送數(shù)據(jù),而不需要客戶端反復(fù)輪詢。
相比于 WebSocket,SSE 更輕量(基于 HTTP 協(xié)議),且支持自動(dòng)重連。它非常適合像實(shí)時(shí)通知、股價(jià)更新、大模型(LLM)流式輸出這類單向推送場(chǎng)景。
核心原理解析
在傳統(tǒng)的 HTTP 請(qǐng)求中,客戶端發(fā)送請(qǐng)求,服務(wù)器返回響應(yīng),然后連接關(guān)閉。而 SseEmitter 改變了這種模式:
- 保持連接:服務(wù)器返回的響應(yīng)頭包含
Content-Type: text/event-stream。 - 分塊傳輸:連接保持打開狀態(tài),服務(wù)器可以多次調(diào)用
emitter.send()發(fā)送數(shù)據(jù)包。 - 結(jié)束生命周期:手動(dòng)調(diào)用
complete()或因?yàn)槌瑫r(shí)/錯(cuò)誤觸發(fā)onTimeout/onError。
前言
在實(shí)時(shí)性需求日益增長(zhǎng)的今天,我們不一定非要?jiǎng)佑?ldquo;重型武器” WebSocket。如果你只需要服務(wù)器向客戶端推送消息(如:進(jìn)度條更新、ChatCompletion 響應(yīng)),Spring 提供的 SseEmitter 可能是你的最佳選擇。
一、 核心代碼實(shí)現(xiàn)
使用 SseEmitter 通常分為三步:創(chuàng)建連接、保存引用、推送消息。
1. 控制器層 (Controller)
這是建立連接的入口。
@RestController
@RequestMapping("/api/sse")
public class SseController {
// 用于存儲(chǔ)已連接的客戶端,實(shí)際生產(chǎn)中建議使用專門的服務(wù)類管理
private static final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
@GetMapping(value = "/subscribe/{userId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter subscribe(@PathVariable String userId) {
// 設(shè)置超時(shí)時(shí)間(單位毫秒),0 表示永不超時(shí)
SseEmitter emitter = new SseEmitter(60_000L);
emitters.put(userId, emitter);
// 注冊(cè)回調(diào)
emitter.onCompletion(() -> emitters.remove(userId));
emitter.onTimeout(() -> emitters.remove(userId));
emitter.onError((e) -> emitters.remove(userId));
// 建立連接后立即發(fā)送一條消息,防止某些代理或?yàn)g覽器認(rèn)為連接已失效
try {
emitter.send(SseEmitter.event().name("INIT").data("Connected!"));
} catch (IOException e) {
emitter.completeWithError(e);
}
return emitter;
}
}
2. 消息推送邏輯
你可以從任何異步線程調(diào)用 send 方法。
public void sendMessage(String userId, String content) {
SseEmitter emitter = emitters.get(userId);
if (emitter != null) {
try {
// 可以發(fā)送純文本,也可以發(fā)送封裝好的對(duì)象(會(huì)自動(dòng)轉(zhuǎn) JSON)
emitter.send(SseEmitter.event()
.id(UUID.randomUUID().toString())
.name("message") // 對(duì)應(yīng)前端的事件名
.data(content)
.reconnectTime(3000)); // 提示客戶端重連間隔
} catch (IOException e) {
emitters.remove(userId);
emitter.completeWithError(e);
}
}
}
二、 前端如何接收?
瀏覽器內(nèi)置了 EventSource API,調(diào)用非常簡(jiǎn)單:
const eventSource = new EventSource('/api/sse/subscribe/user123');
eventSource.onmessage = (event) => {
console.log("收到通用消息:", event.data);
};
// 監(jiān)聽特定類型的事件 (對(duì)應(yīng)后端的 .name("message"))
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log("處理后的數(shù)據(jù):", data);
});
eventSource.onerror = (error) => {
console.error("SSE 錯(cuò)誤:", error);
eventSource.close();
};
三、 避坑指南(關(guān)鍵技巧)
- Nginx 配置:如果你使用了 Nginx 代理,必須配置
proxy_buffering off;和proxy_set_header Connection "";,否則 Nginx 會(huì)緩存消息直到緩沖區(qū)滿才一次性發(fā)給前端。 - 容器線程限制:雖然
SseEmitter是異步的,但它依然占用一個(gè) HTTP 連接。在高并發(fā)場(chǎng)景下,需要調(diào)整 Web 容器(如 Tomcat)的最大連接數(shù)。 - 超時(shí)處理:瀏覽器默認(rèn)會(huì)在 SSE 斷開后自動(dòng)重連。在
onTimeout中,務(wù)必調(diào)用emitter.complete()來清理服務(wù)器資源。 - 跨域問題:確保 CORS 配置允許
Last-Event-ID等特殊請(qǐng)求頭。
總結(jié)
SseEmitter 是 Spring 為我們封裝的一把“輕巧的瑞士軍刀”。它規(guī)避了 WebSocket 復(fù)雜的握手和協(xié)議轉(zhuǎn)換,在處理流式輸出(如 AI 對(duì)話響應(yīng))時(shí)表現(xiàn)極佳。
以上就是使用Spring SseEmitter實(shí)現(xiàn)服務(wù)端推送的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Spring SseEmitter服務(wù)端推送的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IntelliJ?IDEA社區(qū)版2021.3配置SpringBoot項(xiàng)目詳細(xì)教程及失敗案例
IntelliJ?IDEA?2021.3.3是一款集成開發(fā)環(huán)境,用于Java和其他編程語言的開發(fā),下面這篇文章主要給大家介紹了關(guān)于IntelliJ?IDEA社區(qū)版2021.3配置SpringBoot項(xiàng)目詳細(xì)教程及失敗案例的相關(guān)資料,需要的朋友可以參考下2024-03-03
idea本地jar使用maven打包本地依賴實(shí)現(xiàn)自動(dòng)編譯到項(xiàng)目里的操作
這篇文章主要介紹了idea本地jar使用maven打包本地依賴實(shí)現(xiàn)自動(dòng)編譯到項(xiàng)目里的操作,本文通過示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-05-05
Java連接SQL?Server數(shù)據(jù)庫(kù)的超詳細(xì)教程
在Java應(yīng)用程序中我們經(jīng)常需要與數(shù)據(jù)庫(kù)進(jìn)行交互,一種常見的數(shù)據(jù)庫(kù)是Microsoft?SQL?Server,下面這篇文章主要給大家介紹了關(guān)于Java連接SQL?Server數(shù)據(jù)庫(kù)的超詳細(xì)教程,需要的朋友可以參考下2024-01-01
@Valid和@Validated注解校驗(yàn)以及異常處理方式
在Javaweb開發(fā)中,防止數(shù)據(jù)庫(kù)惡意攻擊是至關(guān)重要的,盡管前端校驗(yàn)可以起到一定的篩選作用,但通過工具如postman直接對(duì)后端發(fā)起請(qǐng)求的情況仍然需要后端進(jìn)行嚴(yán)格的數(shù)據(jù)校驗(yàn),Java生態(tài)下,@Valid注解配合SpringBoot提供了一個(gè)便捷高效的后端數(shù)據(jù)校驗(yàn)方案2024-11-11
Java中的synchronized有幾種加鎖方式(實(shí)例詳解)
在Java中,synchronized關(guān)鍵字提供了內(nèi)置的支持來實(shí)現(xiàn)同步訪問共享資源,以避免并發(fā)問題,這篇文章主要介紹了java的synchronized有幾種加鎖方式,需要的朋友可以參考下2024-05-05

