大数跨境
0
0

Spring Boot + JavaScript 实时数据流:2种完整实现

Spring Boot + JavaScript 实时数据流:2种完整实现 Spring全家桶实战案例
2025-09-04
1
导读:Spring Boot + JavaScript 实时数据流:2种完整实现
Spring Boot 3实战案例锦集PDF电子书已更新至130篇!

🎉🎉《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.实战案例

2.1 方案1(StreamingResponseBody

StreamingResponseBody 是 Spring MVC 提供的一个接口,用于异步流式传输响应数据。它允许服务器在处理过程中逐步将数据写入输出流,适用于大文件下载、日志推送或实时数据流场景。常与 ResponseEntity<StreamingResponseBody> 结合使用,需在子线程中执行流式写入,并通过 flush() 主动推送数据到客户端。

private final ObjectMapper objectMapper ;public UserStreamingController(ObjectMapper objectMapper) {  this.objectMapper = objectMapper;}@GetMapping("/stream")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, {streamtrue});    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连接主动向客户端推送实时数据。它支持单向通信、事件流格式传输,适用于实时通知、数据流、聊天应用等场景,能显著提升用户体验。

@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter sseUsers() {  SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);  // 在子线程中发送数据,避免阻塞请求线程  Executors.newSingleThreadExecutor().submit(() -> {    try {      for (User user : datas) {        try {          // 序列化为 JSON          String 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(); // 清空  // 使用 EventSource  const 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 实现超大文件的流式传输

惊呆了!Controller接口返回值支持17种逆天类型

Spring Boot3新特性@RSocketExchange轻松实现消息实时推送

惊呆了!Spring Boot 通过这7种策略实现注入

优雅!自定义注解轻松管理多版本Controller接口

图片
图片
图片
图片
图片
图片
图片
图片
图片

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832