極致流式推送!SpringBoot + ResponseBodyEmitter 讓異步更絲滑
隨著 ChatGPT 的興起,流式輸出技術(shù)逐漸走進(jìn)大眾視野。在技術(shù)社區(qū)中,很多開發(fā)者開始研究和實(shí)踐 SSE(服務(wù)器推送事件)。不過(guò),SSE 仍然存在一定的局限性,而 Spring 提供的 ResponseBodyEmitter 則是一種更簡(jiǎn)便的異步流式輸出方式。雖然 ResponseBodyEmitter 并非新技術(shù),它早在 Spring Framework 4.2 版本就已經(jīng)引入,但在實(shí)際開發(fā)中仍然具有極高的實(shí)用價(jià)值,特別是在處理實(shí)時(shí)數(shù)據(jù)推送時(shí)。
ResponseBodyEmitter 的作用
ResponseBodyEmitter 主要用于異步 HTTP 響應(yīng)處理,其核心優(yōu)勢(shì)在于 支持逐步向客戶端發(fā)送數(shù)據(jù),而不是一次性返回所有內(nèi)容。這種特性特別適用于流式傳輸或需要長(zhǎng)時(shí)間處理的任務(wù),例如:
- 長(zhǎng)輪詢服務(wù)器保持連接開放,待有數(shù)據(jù)時(shí)立即返回。
 - 服務(wù)器推送事件(SSE)持續(xù)向客戶端推送更新信息。
 - 流式傳輸逐步推送大量數(shù)據(jù),如文件下載或?qū)崟r(shí)數(shù)據(jù)流。
 - 異步處理在后臺(tái)執(zhí)行耗時(shí)任務(wù),并實(shí)時(shí)返回處理結(jié)果。
 
典型應(yīng)用場(chǎng)景
在實(shí)際業(yè)務(wù)中,ResponseBodyEmitter 可廣泛應(yīng)用于:
- 實(shí)時(shí)進(jìn)度更新(例如文件上傳進(jìn)度條)
 - 實(shí)時(shí)聊天
 - 股票價(jià)格推送
 - AI 實(shí)時(shí)響應(yīng)
 - 服務(wù)器日志流式輸出
 
實(shí)戰(zhàn):構(gòu)建實(shí)時(shí)日志流
假設(shè)我們需要實(shí)現(xiàn)一個(gè)實(shí)時(shí)日志查看功能,服務(wù)器可以不斷地向客戶端推送最新的日志信息。
創(chuàng)建控制器
首先,在 Spring Boot 3.4 應(yīng)用中創(chuàng)建 LogController,使用 ResponseBodyEmitter 處理流式日志輸出。
package com.icoderoad.controller;
importorg.springframework.http.MediaType;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RestController;
importorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
@RestController
@RequestMapping("/api/log")
publicclassLogController{
    
    @GetMapping(value ="/stream", produces =MediaType.TEXT_EVENT_STREAM_VALUE)
    publicResponseBodyEmitterstreamLogs(){
        ResponseBodyEmitter emitter =newResponseBodyEmitter();
        
        newThread(()->{
            try{
                while(true){
                    String logEntry =getLatestLogEntry();
                    if(logEntry !=null){
                        emitter.send(logEntry);
                    }
                    Thread.sleep(1000);// 每秒推送一次
                }
            }catch(Exception e){
                emitter.completeWithError(e);
            }
        }).start();
        
        return emitter;
    }
    
    privateStringgetLatestLogEntry(){
        return"2025-02-12 12:00:00 - INFO: 用戶成功登錄。";
    }
}運(yùn)行效果:
訪問(wèn) http://localhost:8080/api/log/stream,服務(wù)器將每秒推送一條新的日志條目,前端可以實(shí)時(shí)接收并展示。
ResponseBodyEmitter 的核心方法
- send(Object data)逐步向客戶端發(fā)送數(shù)據(jù),可多次調(diào)用。
 - complete()結(jié)束響應(yīng)流。
 - onTimeout(Runnable callback)連接超時(shí)時(shí)觸發(fā)回調(diào)。
 - onCompletion(Runnable callback)數(shù)據(jù)發(fā)送完成后觸發(fā)回調(diào)。
 
ResponseBodyEmitter 的工作原理
異步數(shù)據(jù)生成與推送
在傳統(tǒng) HTTP 響應(yīng)模式中,服務(wù)器通常需要等待整個(gè)響應(yīng)數(shù)據(jù)生成完成后再返回。而 ResponseBodyEmitter 允許 異步逐步推送數(shù)據(jù),即部分?jǐn)?shù)據(jù)準(zhǔn)備好后立即 send() 給客戶端,提高響應(yīng)速度。
分塊傳輸(Chunked Encoding)
傳統(tǒng) HTTP 響應(yīng)頭需要指定 Content-Length,但 ResponseBodyEmitter 采用 分塊傳輸(Chunked Encoding),無(wú)需指定數(shù)據(jù)總長(zhǎng)度,而是將數(shù)據(jù)分塊推送,客戶端收到后即可立即處理,減少等待時(shí)間。
連接生命周期管理
- 數(shù)據(jù)發(fā)送完畢時(shí)調(diào)用 complete() 關(guān)閉連接,避免資源浪費(fèi)。
 - 若發(fā)生異常,調(diào)用 completeWithError(),通知客戶端連接關(guān)閉。
 
注意事項(xiàng)
- 客戶端支持:大多數(shù)現(xiàn)代瀏覽器和 HTTP 客戶端都支持 ResponseBodyEmitter,但部分舊版本可能存在兼容性問(wèn)題。
 - 超時(shí)管理:建議設(shè)置超時(shí)時(shí)間,避免長(zhǎng)連接占用資源,例如:
 
emitter.onTimeout(() -> emitter.complete());- 線程安全:send() 方法是線程安全的,但仍需合理管理任務(wù)線程,避免資源泄漏。
 - 連接關(guān)閉:務(wù)必調(diào)用 complete() 或 completeWithError() 以釋放資源。
 
ResponseBodyEmitter 與其他流式技術(shù)對(duì)比
技術(shù)方案  | 適用場(chǎng)景  | 優(yōu)缺點(diǎn)  | 
Streaming  | 低級(jí)字節(jié)流傳輸  | 靈活性高,但需要手動(dòng)管理   | 
SSE  | 服務(wù)器推送事件  | 需瀏覽器支持   | 
ResponseBodyEmitter  | 通用 HTTP 流式傳輸  | 兼容性更好,易于與 Spring 集成  | 
在 AI 流式輸出等場(chǎng)景下,ResponseBodyEmitter 相比 SSE 具有更好的 HTTP 兼容性,且使用方式更靈活。
結(jié)語(yǔ)
ResponseBodyEmitter 是 Spring 提供的一種輕量級(jí)流式傳輸解決方案,非常適用于 高并發(fā)、實(shí)時(shí)數(shù)據(jù)推送 需求。無(wú)論是 進(jìn)度條實(shí)時(shí)更新、實(shí)時(shí)聊天、股票數(shù)據(jù)推送、系統(tǒng)日志流式輸出,它都能提供更絲滑的用戶體驗(yàn)。如果你的應(yīng)用需要高效的異步數(shù)據(jù)推送,不妨試試 ResponseBodyEmitter 吧!















 
 
 















 
 
 
 