偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

SpringBoot+ResponseBodyEmitter異步流式推送神技,非常強大!

開發(fā) 前端
簡單來說,它是 Spring 框架提供的一個異步響應發(fā)射器,允許我們像擠牙膏一樣,分批次把數(shù)據(jù)推送給客戶端,而不是一次性把整個牙膏管都扔過去。

兄弟們,當你正在開發(fā)一個在線考試系統(tǒng),用戶提交試卷后需要等待后臺長時間的自動閱卷。這時候如果用傳統(tǒng)的同步接口,頁面就會像被施了定身咒一樣卡在那里,用戶只能對著空白頁面發(fā)呆,甚至懷疑是不是服務器跑路了。

再比如你做一個文件上傳功能,用戶上傳一個 10GB 的大文件,如果不能實時看到上傳進度,很可能會誤以為上傳失敗而反復點擊,導致服務器壓力倍增。

這些場景都指向一個核心問題:傳統(tǒng)同步接口的一次性響應模式已經(jīng)無法滿足現(xiàn)代 Web 應用的實時交互需求。這時候,Spring Boot 提供的 ResponseBodyEmitter 就像一把倚天劍,幫我們劈開阻塞的迷霧。

一、ResponseBodyEmitter 到底是什么?

簡單來說,它是 Spring 框架提供的一個異步響應發(fā)射器,允許我們像擠牙膏一樣,分批次把數(shù)據(jù)推送給客戶端,而不是一次性把整個牙膏管都扔過去。這種特性在以下場景中尤其有用:

  • 實時日志監(jiān)控:運維人員可以實時看到服務器日志滾動
  • AI 流式響應:類似 ChatGPT 的逐字輸出效果
  • 長任務進度條:文件上傳、視頻轉(zhuǎn)碼等耗時操作的實時反饋
  • 股票行情推送:金融系統(tǒng)的實時數(shù)據(jù)更新

用大白話講,它就像一個快遞員,每次只送一小包快遞,送完一包馬上通知你,而不是等所有快遞都打包好了再一次性送過來。這樣你可以更快地收到部分結(jié)果,不用干等。

二、ResponseBodyEmitter 的核心原理

1. 打破傳統(tǒng) HTTP 響應模式

傳統(tǒng) HTTP 響應就像一場單向的演講,服務器講完所有內(nèi)容后就結(jié)束對話。而 ResponseBodyEmitter 則像一場脫口秀,演員(服務器)可以隨時拋出新梗(數(shù)據(jù)),觀眾(客戶端)可以實時互動。

具體來說,它通過 分塊傳輸編碼(Chunked Encoding) 實現(xiàn)流式輸出。服務器不再需要在響應頭中指定 Content-Length,而是把數(shù)據(jù)分成多個小塊,每個小塊都有自己的長度標識??蛻舳耸盏揭粔K就處理一塊,就像吃火鍋時涮毛肚,燙一片吃一片,不用等整鍋燒開。

2. 線程管理的魔法

ResponseBodyEmitter 的 send () 方法是線程安全的,但它本身并不創(chuàng)建線程。我們需要自己啟動一個異步線程來處理耗時操作,避免阻塞主線程。這就像餐館里的服務員,主線程負責接單(接收請求),然后把訂單交給后廚(異步線程)處理,服務員可以繼續(xù)接待其他客人。

3. 連接生命周期的精準控制

  • complete():數(shù)據(jù)全部發(fā)送完畢,優(yōu)雅地關閉連接
  • completeWithError():出現(xiàn)異常時終止連接并傳遞錯誤信息
  • onTimeout():設置超時回調(diào),避免長連接占用資源

就像一場演唱會,主唱(服務器)會在結(jié)束時說 “謝謝大家”(complete ()),如果設備出故障會說 “抱歉,演出取消”(completeWithError ()),如果觀眾長時間沒反應會說 “再不走就關燈啦”(onTimeout ())。

三、實戰(zhàn)案例:打造實時日志監(jiān)控系統(tǒng)

1. 準備工作

添加 Spring Boot Web 依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. 控制器實現(xiàn)

@RestController
@RequestMapping("/api/log")
public class LogController {
    private final Logger logger = LoggerFactory.getLogger(LogController.class);
    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public ResponseBodyEmitter streamLogs() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter(60000L); // 60秒超時
        // 啟動異步線程處理日志生成
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    String logEntry = "[" + LocalDateTime.now() + "] 第" + i + "條日志\n";
                    emitter.send(logEntry); // 發(fā)送日志條目
                    Thread.sleep(1000); // 模擬日志生成延遲
                }
                emitter.complete(); // 日志發(fā)送完畢
            } catch (Exception e) {
                emitter.completeWithError(e); // 出現(xiàn)異常時終止
            }
        });
        // 設置超時回調(diào)
        emitter.onTimeout(() -> {
            logger.warn("日志流連接超時");
            emitter.completeWithError(new TimeoutException("連接超時"));
        });
        // 設置完成回調(diào)
        emitter.onCompletion(() -> logger.info("日志流連接已關閉"));
        return emitter;
    }
}

3. 前端接收數(shù)據(jù)

<!DOCTYPE html>
<html>
<head>
    <title>實時日志監(jiān)控</title>
</head>
<body>
    <div id="log-container"></div>
    <script>
        const logContainer = document.getElementById('log-container');
        // 使用fetch API處理流式響應
        async function fetchLogStream() {
            const response = await fetch('/api/log/stream');
            if (!response.ok) throw new Error('請求失敗');
            const reader = response.body.getReader();
            const decoder = new TextDecoder('utf-8');
            while (true) {
                const { done, value } = await reader.read();
                if (done) break;
                const logText = decoder.decode(value, { stream: true });
                logContainer.innerHTML += `<div>${logText}</div>`;
            }
        }
        fetchLogStream().catch(error => console.error('日志流錯誤:', error));
    </script>
</body>
</html>

4. 效果演示

啟動應用后訪問http://localhost:8080/api/log/stream,頁面會每秒更新一條日志,就像在看一場實時的日志電影。這種體驗比傳統(tǒng)的輪詢方式不知道高到哪里去了!

四、核心方法深度解析

1. send(Object data)

  • 作用:向客戶端發(fā)送數(shù)據(jù),可以多次調(diào)用
  • 注意事項:

數(shù)據(jù)類型可以是任意對象,但建議使用字符串或 JSON 格式

發(fā)送的數(shù)據(jù)會被自動序列化為 JSON,除非指定了 MIME 類型

如果連接已關閉,再次調(diào)用會拋出異常

2. complete()

  • 作用:終止響應流,釋放資源
  • 最佳實踐:

在 finally 塊中調(diào)用,確保即使出現(xiàn)異常也能關閉連接

避免在異步線程中遺漏調(diào)用,導致連接泄漏

3. onTimeout(Runnable callback)

  • 作用:設置超時回調(diào)
  • 參數(shù):

timeout:超時時間(毫秒),默認 30 秒

callback:超時后執(zhí)行的操作

4. onCompletion(Runnable callback)

  • 作用:設置完成回調(diào)
  • 觸發(fā)時機:

調(diào)用 complete () 后

所有數(shù)據(jù)發(fā)送完畢后

出現(xiàn)異常調(diào)用 completeWithError () 后

五、與其他技術的對比分析

1. vs SSE(Server-Sent Events)

特性

ResponseBodyEmitter

SSE

協(xié)議兼容性

任意 HTTP 客戶端

僅支持 SSE 兼容的客戶端

數(shù)據(jù)格式

任意格式

必須符合 SSE 規(guī)范(text/event-stream)

自動重連

不支持

支持

適用場景

通用流式傳輸

事件驅(qū)動型場景

結(jié)論:如果你需要瀏覽器自動重連或嚴格遵循 SSE 規(guī)范,選 SSE;否則 ResponseBodyEmitter 更靈活。

2. vs WebSocket

特性

ResponseBodyEmitter

WebSocket

連接性質(zhì)

單向(服務器→客戶端)

雙向

協(xié)議

HTTP

專用 WebSocket 協(xié)議

實現(xiàn)復雜度

簡單

較高

適用場景

實時數(shù)據(jù)推送

實時聊天、多人協(xié)作

結(jié)論:如果需要雙向通信,WebSocket 是更好的選擇;否則 ResponseBodyEmitter 更輕量。

3. vs StreamingResponseBody

特性

ResponseBodyEmitter

StreamingResponseBody

數(shù)據(jù)發(fā)送方式

主動推送

被動寫入 OutputStream

異步支持

完全異步

部分異步(需手動管理線程)

靈活性

較低

適用場景

動態(tài)生成數(shù)據(jù)

靜態(tài)文件流式傳輸

結(jié)論:如果需要動態(tài)控制數(shù)據(jù)發(fā)送時機,選 ResponseBodyEmitter;否則 StreamingResponseBody 更簡單。

六、實際應用場景精講

1. AI 流式響應(如 ChatGPT 效果)

@RestController
@RequestMapping("/api/ai")
public class AiController {
    @GetMapping("/chat")
    public ResponseBodyEmitter chatWithAi() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter();
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                String[] aiResponse = {
                    "你好!我是AI助手。",
                    "今天有什么可以幫你的?",
                    "我可以回答問題、提供建議,或者陪你聊天。"
                };
                for (String message : aiResponse) {
                    Thread.sleep(1000); // 模擬AI思考時間
                    emitter.send(message);
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}

前端效果:文字像打字機一樣逐字出現(xiàn),用戶體驗更自然。

2. 實時股票行情推送

@RestController
@RequestMapping("/api/stock")
public class StockController {
    private final Map<String, ResponseBodyEmitter> emitters = new ConcurrentHashMap<>();
    @GetMapping("/subscribe/{symbol}")
    public ResponseBodyEmitter subscribe(@PathVariable String symbol) {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter(30000L);
        // 保存發(fā)射器以便后續(xù)推送數(shù)據(jù)
        emitters.put(symbol, emitter);
        // 模擬實時行情更新
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                while (true) {
                    double price = Math.random() * 100; // 隨機生成股價
                    emitter.send("{\"symbol\":\"" + symbol + "\",\"price\":" + price + "}");
                    Thread.sleep(5000); // 每5秒更新一次
                }
            } catch (Exception e) {
                emitter.completeWithError(e);
                emitters.remove(symbol);
            }
        });
        return emitter;
    }
    // 推送消息給所有訂閱者
    public void pushStockUpdate(String symbol, double price) {
        ResponseBodyEmitter emitter = emitters.get(symbol);
        if (emitter != null) {
            try {
                emitter.send("{\"symbol\":\"" + symbol + "\",\"price\":" + price + "}");
            } catch (Exception e) {
                emitter.completeWithError(e);
                emitters.remove(symbol);
            }
        }
    }
}

3. 長任務進度條

@RestController
@RequestMapping("/api/task")
public class TaskController {
    @PostMapping("/start")
    public ResponseBodyEmitter startLongTask() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter();
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                for (int i = 1; i <= 100; i++) {
                    Thread.sleep(100); // 模擬任務執(zhí)行
                    emitter.send("{\"progress\":" + i + "}");
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}

前端可以根據(jù)接收到的 progress 字段更新進度條,讓用戶實時了解任務狀態(tài)。

七、常見問題與解決方案

1. 連接超時問題

  • 現(xiàn)象:客戶端長時間收不到數(shù)據(jù),連接自動斷開
  • 解決方案:

設置合理的超時時間:new ResponseBodyEmitter(60000L)

在異步線程中定期發(fā)送心跳包

使用 onTimeout 回調(diào)處理超時邏輯

2. 數(shù)據(jù)亂序問題

  • 現(xiàn)象:客戶端接收到的數(shù)據(jù)順序與發(fā)送順序不一致
  • 解決方案:

在數(shù)據(jù)中添加時間戳或序列號

確保異步線程按順序發(fā)送數(shù)據(jù)

使用線程安全的隊列管理待發(fā)送數(shù)據(jù)

3. 資源泄漏問題

  • 現(xiàn)象:服務器連接數(shù)不斷增加,最終導致內(nèi)存溢出
  • 解決方案:

確保在 finally 塊中調(diào)用 complete () 或 completeWithError ()

使用 WeakHashMap 管理 Emitter 實例

設置合理的超時時間并在超時后清理資源

4. 瀏覽器緩存問題

  • 現(xiàn)象:刷新頁面后數(shù)據(jù)未更新
  • 解決方案:

在響應頭中添加Cache-Control: no-cache

在請求 URL 中添加隨機參數(shù):/api/log/stream?timestamp=${new Date().getTime()}

八、性能優(yōu)化技巧

1. 線程池管理

  • 推薦做法:
private final ExecutorService executor = Executors.newFixedThreadPool(10);
@GetMapping("/stream")
public ResponseBodyEmitter stream() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    executor.submit(() -> {
        // 異步任務邏輯
    });
    return emitter;
}

使用固定大小的線程池避免頻繁創(chuàng)建銷毀線程。

2. 批量發(fā)送數(shù)據(jù)

  • 場景:需要發(fā)送大量小數(shù)據(jù)塊
  • 優(yōu)化方法:
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < 100; i++) {
    buffer.append("數(shù)據(jù)塊").append(i).append("\n");
    if (i % 10 == 0) { // 每10個數(shù)據(jù)塊發(fā)送一次
        emitter.send(buffer.toString());
        buffer.setLength(0);
    }
}

減少 send () 調(diào)用次數(shù),降低網(wǎng)絡開銷。

3. 壓縮傳輸數(shù)據(jù)

  • 配置方法:
@Bean
public FilterRegistrationBean<HttpEncodingFilter> httpEncodingFilter() {
    FilterRegistrationBean<HttpEncodingFilter> registrationBean = new FilterRegistrationBean<>();
    HttpEncodingFilter filter = new HttpEncodingFilter();
    filter.setForceEncoding(true);
    filter.setEncoding("gzip");
    registrationBean.setFilter(filter);
    registrationBean.addUrlPatterns("/*");
    return registrationBean;
}

使用 Gzip 壓縮減少數(shù)據(jù)傳輸量。

九、總結(jié)

ResponseBodyEmitter 就像 Spring Boot 送給 Java 開發(fā)者的一件神器,它讓我們能夠輕松實現(xiàn)高效的異步流式推送。通過本文的學習,你應該已經(jīng)掌握了以下核心技能:

  • 理解異步流式推送的必要性和應用場景
  • 掌握 ResponseBodyEmitter 的核心方法和使用技巧
  • 學會與其他技術的對比分析和選型策略
  • 了解常見問題的解決方案和性能優(yōu)化技巧

最后,我想說的是,技術的價值不在于它有多復雜,而在于它能解決什么問題。ResponseBodyEmitter 或許不是最耀眼的技術,但它在提升用戶體驗、優(yōu)化系統(tǒng)性能方面的作用卻不可小覷。希望本文能幫助你在實際項目中更好地運用這門神技,讓你的應用程序更加流暢、高效!

責任編輯:武曉燕 來源: 石杉的架構(gòu)筆記
相關推薦

2025-09-02 09:32:33

Spring工具項目

2025-03-10 00:00:11

Spring框架數(shù)據(jù)

2025-03-10 08:44:17

2024-05-09 08:08:32

SpringBinderJava

2025-02-10 08:43:31

Java異步編程

2009-12-29 10:41:30

Ubuntu 9.10

2020-05-21 15:53:59

遠程調(diào)試工具

2022-02-25 15:32:45

SpringBoot代碼優(yōu)化

2024-03-06 08:13:33

FutureJDKCallable

2024-09-03 08:26:59

Spring格式模板

2010-12-09 10:54:32

2021-09-07 07:53:43

工具

2023-05-10 10:35:14

服務器代碼

2024-10-14 09:20:09

異步流式接口

2025-04-22 07:48:56

2025-03-26 00:00:00

Spring服務器推送技術

2024-06-05 10:31:50

2024-09-05 08:58:37

2021-02-05 07:28:11

SpringbootNettyWebsocke

2021-12-31 13:25:44

PythonPipe代碼
點贊
收藏

51CTO技術棧公眾號