实时数据推送:Spring Boot 中两种 SSE 实战方案
在 Web 开发中,实时数据交互变得越来越普遍。无论是股票价格的波动、比赛比分的更新,还是聊天消息的传递,都需要服务器能够及时地将数据推送给客户端。传统的 HTTP 请求-响应模式在处理这类需求时显得力不从心,而服务器推送事件(Server-Sent Events,SSE)为我们提供了一种轻量级且高效的解决方案。
本文将介绍两种基于 Spring Boot 实现 SSE 的方案,并结合代码示例,帮助你快速掌握实时数据推送的技巧。
SSE:轻量级实时数据传输方案
SSE 是一种基于 HTTP 的服务器推送技术,它允许服务器在事件发生时主动将数据推送给客户端,无需客户端不断发起请求。SSE 使用简单的文本格式传输数据,并利用浏览器原生的 EventSource 对象进行处理。
SSE 的优势:
- 简单易用: 基于 HTTP 协议,无需额外学习成本。
- 轻量级: 数据格式简单,传输效率高。
- 浏览器兼容性好: 大多数现代浏览器都支持 SSE。
方案一:Spring WebFlux(或 Reactor ) + SSE,打造响应式实时数据流
Spring WebFlux 是 Spring Framework 5.0 推出的响应式 Web 框架,它基于 Reactor 库,支持异步非阻塞的编程模型,能够高效处理大量并发连接和数据流。
- 异步调用 API: 使用
WebClient或其他异步 HTTP 客户端库异步调用外部 API。 - 使用 Flux 或 Mono 处理响应: 使用 Reactor 的
Flux或Mono类型处理异步 API 的响应数据流。 - 整合到 SSE 流中: 将 API 响应数据整合到已有的 SSE 流中,或者创建一个新的 SSE 流并将数据推送给前端。
1. 添加依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
2. 创建 Controller:
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.reactive.function.client.WebClient;import java.time.Duration;@RestController
public class ChatController {private final WebClient webClient; // 用于调用外部 APIpublic ChatController(WebClient.Builder webClientBuilder) {this.webClient = webClientBuilder.baseUrl("http://api.example.com").build(); }@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<ServerSentEvent<String>> streamChatGPTReply(@RequestParam String message) {// 使用 WebClient 异步调用外部 APIreturn webClient.post().uri("/api/external") .bodyValue(message).retrieve().bodyToFlux(String.class).map(data -> ServerSentEvent.<String>builder().data(data).build()).delayElements(Duration.ofMillis(100)); // 模拟延迟}
}
3. 前端代码示例:
<!DOCTYPE html>
<html>
<head><title>SSE Chat Demo</title>
</head>
<body><h1>SSE Chat</h1><div id="messages"></div><input type="text" id="message" placeholder="Enter message"><button onclick="sendMessage()">Send</button><script>const eventSource = new EventSource('/stream?message=Hello'); // 连接到 SSE 接口eventSource.onmessage = (event) => {document.getElementById('messages').innerHTML += event.data + '<br>'; // 显示接收到的消息};eventSource.onerror = (error) => {console.error('SSE 连接错误:', error);eventSource.close();};function sendMessage() {const message = document.getElementById('message').value;// 发送消息到服务器,这里仅作示例,实际应用中可能需要调用其他 API}</script>
</body>
</html>
方案二:Spring MVC + DeferredResult/Callable,实现异步结果返回
在 Spring MVC 中,我们可以使用 DeferredResult 或 Callable 实现异步结果返回,从而实现服务器推送。
- 创建一个 DeferredResult 或 Callable 对象: 这两个对象都允许异步返回结果。
- 启动一个新线程调用 API: 在新线程中调用外部 API,避免阻塞主线程。
- 设置 DeferredResult 的结果或返回 Callable 的值: 在 API 调用完成后,设置
DeferredResult的结果或返回Callable的值,Spring 会自动将结果发送给前端。
1. 使用 DeferredResult:
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;@RestController
public class DeferredResultController {@GetMapping(value = "/deferred", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public DeferredResult<String> handleRequest(@RequestParam String message) {DeferredResult<String> result = new DeferredResult<>();new Thread(() -> {// 模拟耗时操作,例如调用外部 APIString apiResponse = callExternalAPI(message);// 设置 DeferredResult 的结果result.setResult(apiResponse);}).start();return result;}private String callExternalAPI(String message) {// 模拟调用外部 API 并返回结果return "External API response for: " + message;}
}
2. 使用 Callable:
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.Callable;@RestController
public class CallableController {@GetMapping(value = "/callable", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Callable<String> handleRequest(@RequestParam String message) {return () -> {// 模拟耗时操作,例如调用外部 APIString apiResponse = callExternalAPI(message);return apiResponse;};}private String callExternalAPI(String message) {// 模拟调用外部 API 并返回结果return "External API response for: " + message;}
}
前端代码同上
总结
本文介绍了两种基于 Spring Boot 实现 SSE 的方案:
- Spring WebFlux + SSE: 更适合处理大量并发连接和数据流的场景,代码简洁优雅,更符合响应式编程的思想。
- Spring MVC + DeferredResult/Callable: 更适合处理单个请求的异步结果返回,代码相对简单,但可扩展性有限。
你可以根据具体的业务需求选择合适的方案来实现实时数据推送功能。无论选择哪种方案,SSE 都为我们提供了一种轻量级、高效、易于实现的实时数据传输方案,可以帮助我们构建更加优秀的用户体验.
相关文章:
实时数据推送:Spring Boot 中两种 SSE 实战方案
在 Web 开发中,实时数据交互变得越来越普遍。无论是股票价格的波动、比赛比分的更新,还是聊天消息的传递,都需要服务器能够及时地将数据推送给客户端。传统的 HTTP 请求-响应模式在处理这类需求时显得力不从心,而服务器推送事件&a…...
数据守护者:SQL一致性检查的艺术与实践
标题:数据守护者:SQL一致性检查的艺术与实践 在数据驱动的商业世界中,数据的一致性是确保决策准确性和业务流程顺畅的关键。SQL作为数据查询和操作的基石,提供了多种工具来维护数据的一致性。本文将深入探讨如何使用SQL进行数据一…...
jenkins配置+vue打包多环境切换
jenkins配置流水线过程 1.新建item 加入相关的参数就行了。 流水线脚本设置 后端脚本 node {stage checkoutsh"""#每次打包清空工作空间目录rm -rf $workspace/*cd $workspace#到工作空间下从远端svn服务端拉取代码svn co svn://10.1.19.21/repo/技术中台/低…...
idea和jdk的安装教程
1.JDK的安装 下载 进入官网,找到你需要的JDK版本 Java Downloads | Oracle 中国 我这里是windows的jdk17,选择以下 安装 点击下一步,安装完成 配置环境变量 打开查看高级系统设置 在系统变量中添加两个配置 一个变量名是 JAVA_HOME …...
HTML静态网页成品作业(HTML+CSS)——电影网首页网页设计制作(1个页面)
🎉不定期分享源码,关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 🏷️本套采用HTMLCSS,未使用Javacsript代码,共有1个页面。 二、作品演示 三、代…...
大数据系列之:Flink Doris Connector,实时同步数据到Doris数据库
大数据系列之:Flink Doris Connector,实时同步数据到Doris数据库 一、版本兼容性二、使用三、Flink SQL四、DataStream五、Lookup Join六、配置通用配置项接收器配置项查找Join配置项 七、Doris 和 Flink 列类型映射八、使用Flink CDC访问Doris的示例九、…...
LabVIEW VI 多语言动态加载与运行的实现
在多语言应用程序开发中,确保用户界面能够根据用户的语言偏好动态切换是一个关键需求。本文通过分析一个LabVIEW程序框图,详细说明了如何使用LabVIEW中的属性节点和调用节点来实现VI(虚拟仪器)界面语言的动态加载与运行。此程序允…...
Unity引擎基础知识
目录 Unity基础知识概要 1. 创建工程 2. 工程目录介绍 3. Unity界面和五大面板 4. 游戏物体创建与操作 5. 场景和层管理 6. 组件系统 7. 脚本语言C# 8. 物理引擎和UI系统 学习资源推荐 Unity引擎中如何优化大型游戏项目的性能? Unity C#脚本语言的高级编…...
练习题- 探索正则表达式对象和对象匹配
正则表达式(Regular Expressions)是一种强大而灵活的文本处理工具,它允许我们通过模式匹配来处理字符串。这在数据清理、文本分析等领域有着广泛的应用。在Python中,正则表达式通过re模块提供支持,学习和掌握正则表达式对于处理复杂的文本数据至关重要。 本文将探索如何在…...
Java集合提升
1. 手写ArrayList 1.1. ArrayList底层原理细节 底层结构是一个长度可以动态增长的数组(顺序表)transient Object[] elementData; 特点:在内存中分配连续的空间,只存储数据,不存储地址信息。位置就隐含着地址。优点 节…...
uniapp 微信小程序生成水印图片
效果 源码 <template><view style"overflow: hidden;"><camera device-position"back" flash"auto" class"camera"><cover-view class"text-white padding water-mark"><cover-view class"…...
ElasticSearch相关知识点
ElasticSearch中的倒排索引是如何工作的? 倒排索引是ElasticSearch中用于全文检索的一种数据结构,与正排索引不同的是,正排索引将文档按照词汇顺序组织。而倒排索引是将词汇映射到包含该词汇的文档中。 在ElasticSearch中,倒排索…...
css 文字图片居中及网格布局
以下内容纯自已个人理解,直接上代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…...
解决ImportError: DLL load failed while importing _rust: 找不到指定的程序
解决ImportError: DLL load failed while importing _rust: 找不到指定的程序 python使用库cryptography 当 from cryptography.hazmat.bindings._rust import exceptions as rust_exceptions 时,会报错: ImportError: DLL load failed while importin…...
集合-List去重
1.利用Set去重 @Test public void distinctList() {List<String> oldList = new ArrayList<>();oldList.add("a");oldList.add("a");oldList.add("b");oldList.add("c");oldList.add("d");List<String> …...
ST-LINK USB communication error 非常有效的解决方法
文章目录 一、检查确定是ST-LINK USB communication error的问题二、关闭文件,打开keil软件所在文件夹,找到STLink文件夹,找到该应用程序双击 一、检查确定是ST-LINK USB communication error的问题 二、关闭文件,打开keil软件所在…...
探索CSS的:future-link伪类:选择指向未来文档的链接
CSS(层叠样式表)是Web设计中用于描述网页元素样式的语言。随着CSS4的提案,引入了许多新的选择器,其中之一是:future-link伪类。然而,需要注意的是,:future-link伪类目前还处于提议阶段,并没有在…...
【C++】序列与关联容器(三)map与multimap容器
【C】序列与关联容器(三)map与multimap容器 一、map二、multiset / multimap 一、map 树中的每个结点的类型是一个std::pair //pair的类型是<const key,value> pair是一个包含两个指针的结构体,第一个指针指向该节点的key,…...
ActiveMQ、RabbitMQ、Kafka、RocketMQ在优先级队列、延迟队列、死信队列、重试队列、消费模式、广播模式的区别
ActiveMQ、RabbitMQ、Kafka、RocketMQ这四款消息队列在优先级队列、延迟队列、死信队列、重试队列、消费模式、广播模式等方面各有其特点和差异。以下是对这些方面的详细比较: 1. 优先级队列 ActiveMQ:支持优先级队列,可以在发送消息时指定…...
首款会员制区块链 Geist 介绍
今天,Pixelcraft Studios 很高兴地宣布即将推出 Geist,这是一个由 Base、Arbitrum、Alchemy 以及 Aavegotchi 支持的全新 L3。 Geist 之前的代号为 “Gotchichain”,是首个专为游戏打造的会员专用区块链。 为什么选择 Geist? …...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
加密通信 + 行为分析:运营商行业安全防御体系重构
在数字经济蓬勃发展的时代,运营商作为信息通信网络的核心枢纽,承载着海量用户数据与关键业务传输,其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级,传统安全防护体系逐渐暴露出局限性&a…...
向量几何的二元性:叉乘模长与内积投影的深层联系
在数学与物理的空间世界中,向量运算构成了理解几何结构的基石。叉乘(外积)与点积(内积)作为向量代数的两大支柱,表面上呈现出截然不同的几何意义与代数形式,却在深层次上揭示了向量间相互作用的…...
Xcode 16 集成 cocoapods 报错
基于 Xcode 16 新建工程项目,集成 cocoapods 执行 pod init 报错 ### Error RuntimeError - PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup from attributes: {"isa">"PBXFileSystemSynchro…...
