SpringBoot + ResponseBodyEmitter 实时异步流式推送,优雅!
ChatGPT 的火爆,让流式输出技术迅速走进大众视野。在那段时间里,许多热爱钻研技术的小伙伴纷纷开始学习和实践 SSE 异步处理。
我当时也写过相关文章,今天,咱们换一种更为简便的方式来实现流式输出,那就是 ResponseBodyEmitter。
其实,ResponseBodyEmitter 并非新技术,早在 Spring Framework 4.2 版本就已被引入。直到最近,我们在开发一个滚动日志输出功能时,才深入了解到它的强大之处。
ResponseBodyEmitter 的作用
相较于 SSE 技术,ResponseBodyEmitter 更加简单易用。它主要用于处理异步的 HTTP 响应,其核心优势在于 允许逐步将数据发送到客户端,而非一次性发送所有内容。这一特性使得它在需要长时间处理或进行流式传输的场景中表现出色。需要注意的是,ResponseBodyEmitter 本质上是一个接口。
使用场景
- 长轮询:服务器在有数据时会立即响应客户端请求,若暂无数据,则保持连接开放,等待数据到来。
- **服务器推送事件 (SSE)**:服务器能够持续不断地向客户端推送各类事件,实现实时交互。
- 流式传输:可逐步发送大量数据,像文件下载或者实时数据流传输等场景都适用。
- 异步处理:在处理耗时任务时,能逐步返回处理结果,避免客户端长时间等待,提升用户体验。
业务场景举例
在实际业务中,ResponseBodyEmitter 有着广泛的应用,比如进度条的实时更新、实时聊天功能、股票价格的实时更新、系统日志的流式输出以及 AI 的流式响应等。
实时日志流实战
接下来,我们通过一个简单的实时日志流功能,来深入了解 ResponseBodyEmitter 的使用。假设我们有一个应用程序,需要实时查看服务器的日志,以便快速定位和解决问题。
创建控制器
首先,我们在 Spring Boot 应用中创建一个控制器,借助 ResponseBodyEmitter 实现实时日志流。
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
@RestController
@RequestMapping("/api/log")
publicclass LogController {@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public ResponseBodyEmitter streamLogs() {ResponseBodyEmitter emitter = new ResponseBodyEmitter();// 开启异步线程处理数据并发送new Thread(() -> {try {while (true) {String logEntry = getLatestLogEntry();if (logEntry != null) {emitter.send(logEntry);}// 每秒检查一次日志更新Thread.sleep(1000); }} catch (Exception e) {// 出现异常时结束响应并传递错误信息emitter.completeWithError(e); }}).start();return emitter;}private String getLatestLogEntry() {// 模拟从日志文件中获取最新日志条目return"2025-02-12 12:00:00 - INFO: User logged in successfully.";}
}
运行效果
当我们启动这个应用程序,并访问 /api/log/stream 路径时,就能看到一个实时更新的日志流。服务器会每秒向客户端推送一条新的日志条目,客户端会将其显示在页面上,效果如下:

运行效果
ResponseBodyEmitter 的核心方法
-
send(Object data):向客户端发送数据,该方法可以多次调用,实现数据的逐步发送。 -
complete():用于结束响应流,表示数据已经全部发送完毕。 -
onTimeout(Runnable callback):设置超时回调函数,当连接超时时,会执行该回调。 -
onCompletion(Runnable callback):设置完成回调函数,当数据发送完成后,会执行该回调。
ResponseBodyEmitter 工作原理
异步数据生成与推送
在传统的 HTTP 请求 - 响应模式中,服务器通常需要等待整个响应数据生成完成后,才会将其一次性发送给客户端。而 ResponseBodyEmitter 打破了这种模式,它允许服务端在任务执行过程中异步地生成响应数据。
当有部分数据准备好时,就可以立即调用 send() 方法将这些数据推送给客户端,而无需等待整个任务完成。这就好比一场接力赛,每完成一段赛程(生成一部分数据),就马上将接力棒(数据)传递给客户端,大大提高了数据传输的实时性。
分块传输机制
ResponseBodyEmitter 采用了 HTTP 的分块编码(Chunked Encoding)方式来传输数据。在传统的 HTTP 响应中,通常需要在响应头中明确指定 Content-Length,表示整个响应数据的长度。但在分块传输中,服务器不会提前设置 Content-Length,而是将数据分成多个独立的块,每个块都有自己的长度标识。
客户端在接收到数据块后,可以立即对其进行处理,而不必等待整个响应数据接收完毕。这种方式使得数据可以边生成边传输,减少了客户端的等待时间,提高了用户体验。
连接生命周期管理
为了确保资源的合理使用,ResponseBodyEmitter 提供了对连接生命周期的有效管理。当所有数据都发送完毕后,需要调用 complete() 方法来明确告知客户端响应结束,关闭连接。如果在数据传输过程中出现异常,可以调用 completeWithError() 方法,结束响应并向客户端传递错误信息。
这样可以避免连接长时间保持开放,造成资源浪费。
注意事项
- 客户端支持:虽然大多数浏览器和 HTTP 客户端库都支持分块传输,但某些老旧的客户端可能存在兼容性问题。
- 超时设置:为避免长连接长时间占用资源,可以为
ResponseBodyEmitter设置超时时间,示例代码如下:
emitter.onTimeout(() -> emitter.complete());
- 线程安全:
ResponseBodyEmitter的send()方法是线程安全的,但在使用时需要注意控制任务线程的生命周期,避免出现资源泄漏。 - 连接关闭:务必确保在任务结束时调用
complete()或completeWithError()方法,否则可能导致连接无法正常关闭,造成资源浪费。
与 Streaming 和 SSE 的对比
- Streaming:直接通过
OutputStream向客户端写入数据,灵活性较高,但需要手动处理流的关闭,增加了开发的复杂度。 - Server-Sent Events (SSE):基于
text/event-stream协议,适用于服务端事件推送场景,但要求客户端支持 SSE 协议。 - ResponseBodyEmitter:通用性更强,适用于任何支持 HTTP 的客户端,并且易于与 Spring 框架集成,是一种更为便捷的流式传输解决方案。
在处理类似 AI 这种响应式的流式输出场景时,相较于 SSE,ResponseBodyEmitter 作为 Spring 提供的轻量级流式传输解决方案,在 HTTP 协议兼容性方面表现更优。
小结
ResponseBodyEmitter 是 Spring 框架提供的轻量级流式传输解决方案,它能够显著提升高并发和实时性场景下的用户体验。通过 ResponseBodyEmitter,我们可以轻松实现服务器向客户端的实时数据推送。
无论是进度条的实时更新、实时聊天、股票价格的实时监控还是系统日志的流式输出,ResponseBodyEmitter 都能帮助我们构建更加动态和互动的应用程序。
相关文章:
SpringBoot + ResponseBodyEmitter 实时异步流式推送,优雅!
ChatGPT 的火爆,让流式输出技术迅速走进大众视野。在那段时间里,许多热爱钻研技术的小伙伴纷纷开始学习和实践 SSE 异步处理。 我当时也写过相关文章,今天,咱们换一种更为简便的方式来实现流式输出,那就是 Respon…...
网络运维学习笔记(DeepSeek优化版) 016 HCIA-Datacom综合实验01
文章目录 综合实验1实验需求总部特性 分支8分支9 配置一、 基本配置(IP二层VLAN链路聚合)ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 单臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 综合实…...
02 windows qt配置ffmpeg开发环境搭建
版本说明 首先我使用ffmpeg版本是4.2.1 QT使用版本5.14.2 我选择是c编译 在02Day.pro⾥⾯添加ffmpeg头⽂件和库⽂件路径 win32 { INCLUDEPATH $$PWD/ffmpeg-4.2.1-win32-dev/include LIBS $$PWD/ffmpeg-4.2.1-win32-dev/lib/avformat.lib \$$PWD/ffmpeg-4.2.1-win32-dev/l…...
vue-treeselect 【单选/多选】的时候只选择最后一层(绑定的值只绑定最后一层)
欢迎访问我的个人博客 |snows_ls BLOGhttp://snows-l.site 一、单选 1、问题: vue-treeselect 单选的时候只选择最后一层(绑定的值只绑定最后一层) 2、方法 1、只需要加上 :disable-branch-nodes"true" 就行࿰…...
焊接机器人与线激光视觉系统搭配的详细教程
以下是关于焊接机器人与线激光视觉系统搭配的详细教程,包含核心程序框架、调参方法及源码实现思路。本文综合了多个技术文档与专利内容,结合工业应用场景进行系统化总结。 一、系统硬件配置与视觉系统搭建 1. 硬件组成 焊接机器人系统通常由以下模块构…...
微信小程序实现根据不同的用户角色显示不同的tabbar并且可以完整的切换tabbar
直接上图上代码吧 // login/login.js const app getApp() Page({/*** 页面的初始数据*/data: {},/*** 生命周期函数--监听页面加载*/onLoad(options) {},/*** 生命周期函数--监听页面初次渲染完成*/onReady() {},/*** 生命周期函数--监听页面显示*/onShow() {},/*** 生命周期函…...
Git 本地常见快捷操作
Git 本地常见快捷操作 📌 1. 基本操作 操作命令初始化 Git 仓库git init查看 Git 状态git status添加所有文件到暂存区git add .添加指定文件git add <file>提交更改git commit -m "提交信息"修改最后一次提交信息git commit --amend -m "新…...
Elastic Stack 8.16.0 日志收集平台的搭建
简介 1.1 ELK 介绍 ELK 是 Elasticsearch、Logstash、Kibana 三款开源工具的首字母缩写,构成了一套完整的日志管理解决方案,主要用于日志的采集、存储、分析与可视化。 1)Logstash:数据管道工具,负责从…...
江科大51单片机笔记【16】AD/DA转换(下)
写在前言 此为博主自学江科大51单片机(B站)的笔记,方便后续重温知识 在后面的章节中,为了防止篇幅过长和易于查找,我把一个小节分成两部分来发,上章节主要是关于本节课的硬件介绍、电路图、原理图等理论知识…...
Podman 运行redis 报错
Podman 运行redis 报错 一、报错内容 find: .: Permission denied chown: changing ownership of .: Permission denied二、问题分析 SELinux 模式 SELinux(Security-Enhanced Linux)是一种安全模块,旨在通过强制访问控制(MAC)来增强 Linux 系统的安全性。SELinux 具有…...
基于深度学习的多模态人脸情绪识别研究与实现(视频+图像+语音)
这是一个结合图像和音频的情绪识别系统,从架构、数据准备、模型实现、训练等。包括数据收集、预处理、模型训练、融合方法、部署优化等全流程。确定完整系统的组成部分:数据收集与处理、模型设计与训练、多模态融合、系统集成、部署优化、用户界面等。详…...
WordPress the_category与single_cat_title的区别
在wordpress网站主题开发用常会用到调用分类目录的名称,the_category与single_cat_title都可以调用出分类目录的名称。 <?php single_cat_title(); ?> <?php the_category(); ?>但是,不少人搞不清楚二者有什么区别,其实很简…...
以太坊AI代理与PoS升级点燃3月市场热情,2025年能否再创新高?
币热网深度报道:以太坊AI代理与PoS升级引爆3月热潮,2025年能否再攀历史新高? 原文来源:币热网 - 区块链信息资讯平台 以太坊升级,市场热情高涨 近期,以太坊市场犹如被一股神秘力量点燃,掀起了…...
Go语言入门基础详解
一、语言历史背景 Go语言由Google工程师Robert Griesemer、Rob Pike和Ken Thompson于2007年设计,2009年正式开源。设计目标: 兼具Python的开发效率与C的执行性能内置并发支持(goroutine/channel)简洁的类型系统现代化的包管理跨…...
Web网页制作(静态网页):千年之恋
一、是用的PyCharm来写的代码 二、代码中所用到的知识点(无 js) 这段HTML代码展示了一个简单的注册页面,包含了多个HTML元素和CSS样式的应用。 这段HTML代码展示了一个典型的注册页面,包含了常见的HTML元素和表单控件。通过CSS样…...
JVM 的不同组成部分分别有什么作用?
JVM(Java Virtual Machine)主要由以下几个核心组成部分构成: 1. 类加载器子系统 (Class Loader Subsystem): 作用: 加载类: 负责查找并加载 Java 类文件(.class 文件)到 JVM 中。 类文件可以来…...
正则表达式 - 修饰符
正则表达式 - 修饰符 正则表达式是一种强大的文本处理工具,它广泛应用于数据验证、文本搜索、替换、匹配等场景。在正则表达式中,修饰符是用于调整正则表达式行为的特殊字符。本文将详细解析正则表达式中的常用修饰符,帮助读者更好地理解和运用正则表达式。 1. 字符串定位…...
K8s 1.27.1 实战系列(十二)Ingress
一、Ingress介绍 1、Ingress 的核心概念 Ingress 是 Kubernetes 中管理外部流量访问集群内服务的 API 对象,通过 7 层(HTTP/HTTPS)协议实现流量路由、负载均衡和 SSL 终止。它弥补了传统 Service(如 NodePort 和 LoadBalancer)的不足,例如端口占用过多、不支持路径路由…...
tomcat应用的作用以及安装,以及tomcat软件的开机自启动。
一.tomcat介绍 1.作用 tomcat是一款用来部署网站服务器的一款软件。 动态网站主流语言: PHP, lamp/lnmp平台 Java语言,运行在tomcat平台。【只要这个网站或者软件是Java语言写的,我们都可以在tomcat平台上去运行这个java程序。】 网站是…...
Unity中WolrdSpace下的UI展示在上层
一、问题描述 Unity 中 Canvas使用World Space布局的UI,想让它不被3d物体遮挡,始终显示在上层。 二、解决方案 使用shader解决 在 UI 的材质中禁用深度测试(ZTest),强制 UI 始终渲染在最上层。 Shader "Custo…...
postgresql 数据库使用
目录 索引 查看索引 创建 删除索引 修改数据库时区 索引 查看索引 select * from pg_indexes where tablenamet_table_data; 或者 select * from pg_statio_all_indexes where relnamet_table_data; 创建 CREATE INDEX ix_table_data_time ON t_table_data (id, crea…...
Redis的缓存雪崩、缓存击穿、缓存穿透与缓存预热、缓存降级
一、缓存雪崩: 1、什么是缓存雪崩: 如果缓在某一个时刻出现大规模的key失效,那么就会导致大量的请求打在了数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果…...
【SpringMVC】常用注解:@RequestParam
1.作用 如果控制器标注的方法的参数名称与前端传递过来的参数名称不一致,使得SpringMVC无法自动启动,那么我们可以使用该注解实现前后端参数的绑定。 2.相关属性 value/name:这两个属性都是一个作用,都是描述参数的名称&#x…...
JVM垃圾收集器相关面试题(1)
垃圾收集与内存管理摘要 一.核心垃圾收集算法对比 算法原理优点缺点适用场景标记-清除两次遍历(标记存活对象→清除未标记对象)实现简单内存碎片化、双遍历效率低老年代(结合整理)标记-复制内存对半分,存活对象复制到…...
详解SQL数据查询功能
数据查询 一、 单表查询1. 选择表中的若干列2. 选择表中的若干元组3. ORDER BY 子句4. 聚合函数5. GROUP BY 子句6. LIMIT 子句综合示例: 二、 多表查询1. 等值连接查询 (Equi-Join)2. 非等值连接查询 (Non-Equi Join)3. 自然连接查询 (Natural Join)4. 复合条件连接…...
车载以太网测试-11【网络层-ICMP协议】
目录 1 摘要2 ICMP协议帧结构2.1 IP头部2.2 ICMP头部2.3 ICMP数据部分2.4 示例:ICMP回显请求(Ping请求) 3 ICMP协议在车载以太网的应用3.1 为什么需要ICMP?3.1.1 网络连通性测试3.1.2 错误报告3.1.3 网络性能监测3.1.4 路径MTU发现…...
leetcode:728. 自除数(python3解法)
难度:简单 自除数 是指可以被它包含的每一位数整除的数。 例如,128 是一个 自除数 ,因为 128 % 1 0,128 % 2 0,128 % 8 0。 自除数 不允许包含 0 。 给定两个整数 left 和 right ,返回一个列表ÿ…...
【QT】-一文读懂抽象类
抽象类(Abstract Class)是面向对象编程中的一个概念,指的是无法被实例化的类,它通常作为其他类的基类。抽象类的作用是定义一个接口(或约定),让派生类(继承自抽象类的类)来实现具体的功能。 抽象类的特点: 包含纯虚函数(Pure Virtual Function): 抽象类通常包含一…...
vue3-computed计算属性和reactive响应式系统结合使用
1.前言 vue3中使用reactive函数创建一个响应式对象,当对象数据发生变化的时候,依赖这些数据的计算属性和模板会自动的更新。 2.实例 2.1 简写 <template><div><p>用户名: {{ userName }}</p><p>用户名的大写形式: {{ u…...
Dubbo请求调用本地服务
文章目录 前言配置 Dubbo 本地调用方法一:application.yml 中配置方法二:DubboReference 注解中指定 注意个人简介 前言 在微服务架构中,Apache Dubbo 作为一款高性能、轻量级的 RPC 框架,被广泛应用于分布式系统。通常ÿ…...
