🎉🎉《Spring Boot实战案例合集》目前已更新165个案例,我们将持续不断的更新。文末有电子书目录。
💪💪永久更新承诺
我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。
💌💌如何获取
订阅我们的合集《点我订阅》,并通过私信联系我们,我们将第一时间将电子书发送给您。
环境:SpringBoot3.4.2
1. 简介
在构建实时 Web 应用时,传统的请求-响应模式已无法满足数据持续推送的需求。Spring Boot 提供了多种服务端流式方案:StreamingResponseBody 基于原始输出流,适用于大文件下载或自定义流传输,灵活性高但需手动 flush;SseEmitter 遵循 Server-Sent Events(SSE)标准,专为浏览器单向推送设计,支持事件类型、消息 ID 和自动重连,适合实时通知、日志流等场景;而 WebSocket 则提供全双工通信,适用于聊天、协同编辑等高频双向交互场景。三者各有定位:SSE 简单轻量、基于 HTTP、浏览器原生支持;WebSocket 功能强大但开销较大;StreamingResponseBody 更偏向底层流控制。
本篇文章将重点介绍 StreamingResponseBody 与 SseEmitter 的前后端实现,对比其在实时数据流中的应用差异,帮助开发者按需选型。
2.1 方案1(StreamingResponseBody)
StreamingResponseBody 是 Spring MVC 提供的一个接口,用于异步流式传输响应数据。它允许服务器在处理过程中逐步将数据写入输出流,适用于大文件下载、日志推送或实时数据流场景。常与 ResponseEntity<StreamingResponseBody> 结合使用,需在子线程中执行流式写入,并通过 flush() 主动推送数据到客户端。
private final ObjectMapper objectMapper ;public UserStreamingController(ObjectMapper objectMapper) {this.objectMapper = objectMapper;}public ResponseEntity<StreamingResponseBody> streamUsers() {StreamingResponseBody responseBody = os -> {datas.forEach(user -> {try {String json = this.objectMapper.writeValueAsString(user) + "\n";os.write(json.getBytes());os.flush() ;// 模拟延迟TimeUnit.MILLISECONDS.sleep(500) ;} catch (Exception e) {throw new RuntimeException(e);}}) ;} ;return ResponseEntity.ok().header("Content-Type", "text/plain;charset=utf-8").body(responseBody);}
前端读取实现
async function fetchStream() {const response = await fetch('/users/stream');if (!response.body) {console.error('ReadableStream not supported');return;}let container = document.querySelector('#stream_data')// 清空所有子元素// 方案1// container.innerHTML = ""// 方案2// while (container.firstChild) {// container.removeChild(container.firstChild);// }// 方案3// 专为"替换/清空子元素"设计,比上面2个方案性能更好container.replaceChildren()const reader = response.body.getReader();const decoder = new TextDecoder('utf-8');let buffer = ''; // 用于拼接不完整的文本片段while (true) {const {done, value} = await reader.read();if (done) {console.log('Stream complete');break;}// 将 Uint8Array 解码为字符串const chunk = decoder.decode(value, {stream: true});buffer += chunk;// 按行处理const lines = buffer.split('\n');buffer = lines.pop(); // 最后一行可能不完整,留在 buffer 中lines.forEach(line => {if (line) {appendData(container, line);}});}// 处理最后可能残留的不完整行if (buffer) {appendData(container, buffer)}}function appendData(container, data) {let liNode = document.createElement("li");let dataNode = document.createTextNode(data)liNode.appendChild(dataNode)container.appendChild(liNode)}
HTML页面
<body><button type="button" onclick="fetchStream()">获取数据</button><button type="button" onclick="fetchSse()">SSE获取数据</button><hr /><ul id="stream_data"></ul></body>
效果
关于 StreamingResponseBody 更多用法,请查看下面文章:
告别内存溢出!Spring StreamingResponseBody 三大实战案例,性能提升100%
2.2 方案2(SSE)
SseEmitter是Spring用于实现服务器推送(Server-Sent Events, SSE)的类,允许服务器通过HTTP连接主动向客户端推送实时数据。它支持单向通信、事件流格式传输,适用于实时通知、数据流、聊天应用等场景,能显著提升用户体验。
public SseEmitter sseUsers() {SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);// 在子线程中发送数据,避免阻塞请求线程Executors.newSingleThreadExecutor().submit(() -> {try {for (User user : datas) {try {// 序列化为 JSONString json = objectMapper.writeValueAsString(user);// 发送 SSE 消息emitter.send(SseEmitter.event().id(UUID.randomUUID().toString()) // 可选:消息ID.name("user-data") // 可选:事件类型.data(json) // 数据体.reconnectTime(5000) // 可选:重连时间);// 模拟延迟Thread.sleep(300);} catch (JsonProcessingException e) {// JSON 序列化失败,发送错误事件emitter.send(SseEmitter.event().name("error").data("JSON error: " + e.getMessage()));} catch (IOException e) {// 客户端断开等 IO 异常emitter.completeWithError(e);return;} catch (InterruptedException e) {Thread.currentThread().interrupt();emitter.completeWithError(e);return;}}// 先发送完成事件emitter.send(SseEmitter.event().name("complete").data("done").id(UUID.randomUUID().toString()));// 所有数据发送完成emitter.complete();} catch (Exception e) {emitter.completeWithError(e);}});return emitter;}
前端读取实现
async function fetchSse() {const container = document.querySelector('#stream_data');container.replaceChildren(); // 清空// 使用 EventSourceconst eventSource = new EventSource('/users/sse');eventSource.onopen = (event) => {console.log('SSE 连接已建立');};// 监听自定义事件(name = "user-data")eventSource.addEventListener('user-data', function (event) {appendData(container, `${event.data}`);});// ✅ 监听完成事件eventSource.addEventListener('complete', function (event) {appendData(container, '✅ 所有数据加载完毕');// 主动关闭连接,避免继续重连eventSource.close();});// 监听错误eventSource.onerror = function (event) {console.error('SSE 错误:', event);if (eventSource.readyState === EventSource.CLOSED) {console.log('连接已关闭');}};}
效果
推荐文章
Spring Boot参数验证这样做!10个技巧你知道几个?
性能提升!@Async与CompletableFuture优雅应用
高级开发!Spring Boot 基于注解实现字段级权限控制
推荐学习!玩转Spring Boot配置文件加载,完全自定义底层实现
高级开发!一个注解动态控制Controller接口,支持实时更新
效率翻倍!99%开发者忽略的23个Spring核心工具,用着真爽
高并发!Spring Boot 响应式 SSE 实时推送,单机吞吐量10万+
基于2种技术栈!Spring Boot 实现超大文件的流式传输
Spring Boot3新特性@RSocketExchange轻松实现消息实时推送


