SseEmitter 是什么
文章目录
好的,这个任务很简单,我直接为你整理一篇脱敏后的 CSDN 博客文章。已移除所有公司、项目、包名、业务相关信息,替换为通用示例代码。
Spring Boot 中的 SseEmitter 详解:轻松实现服务端流式推送(附 AI 打字机效果实战)
关键词:SseEmitter、Server-Sent Events、SSE、Spring Boot、流式响应、AI 流式输出
一、前言
在使用 ChatGPT 等 AI 产品时,你一定见过"打字机效果"——回答一个字一个字地蹦出来,而不是等全部生成完才显示。这背后最常用的技术之一就是 SSE(Server-Sent Events),而在 Spring 生态中,实现它的核心类就是 SseEmitter。
本文带你彻底搞懂 SseEmitter 的原理、用法和踩坑点。
二、SseEmitter 是什么?
SseEmitter 是 Spring MVC 提供的一个类,全限定名为:
org.springframework.web.servlet.mvc.method.annotation.SseEmitter
它用于实现 SSE(Server-Sent Events,服务器推送事件)——一种基于普通 HTTP 协议、让服务端通过一个长连接持续、单向地向客户端推送数据的技术。
与普通 HTTP 请求的区别
普通的 HTTP 请求是"一问一答":
客户端 ──请求──► 服务端
客户端 ◄──响应── 服务端(连接关闭)
而 SSE 是"一问多答":
客户端 ──发起一次请求──► 服务端
客户端 ◄─ 数据片段1 ◄─ 数据片段2 ◄─ 数据片段3 ... ◄─ 完成/关闭
Controller 方法返回 SseEmitter 后,HTTP 连接不会立即关闭,服务端可以在任意时刻(通常是异步线程中)不断向客户端推送消息。
三、核心 API 一览
| 方法 | 作用 |
|---|---|
new SseEmitter() |
创建实例,使用默认超时时间 |
new SseEmitter(timeoutMs) |
创建实例并指定超时时间(毫秒),0L 表示永不超时 |
emitter.send(data) |
推送一条消息 |
emitter.send(SseEmitter.event().name("xxx").data(...)) |
推送带事件名的消息 |
emitter.complete() |
正常结束连接 |
emitter.completeWithError(e) |
以异常方式结束连接 |
emitter.onTimeout(Runnable) |
超时回调 |
emitter.onCompletion(Runnable) |
完成回调 |
emitter.onError(Consumer) |
出错回调 |
四、快速上手:一个最小示例
1. 后端 Controller
@RestController
@RequestMapping("/sse")
public class SseDemoController {
// 建议使用线程池,避免每次都 new Thread
private final ExecutorService executor = Executors.newCachedThreadPool();
@GetMapping("/stream")
public SseEmitter stream() {
// 设置 60 秒超时
SseEmitter emitter = new SseEmitter(60_000L);
executor.execute(() -> {
try {
for (int i = 1; i <= 5; i++) {
// 推送带事件名的消息
emitter.send(SseEmitter.event()
.name("message")
.data("这是第 " + i + " 条推送"));
Thread.sleep(1000);
}
emitter.complete(); // 正常结束
} catch (Exception e) {
emitter.completeWithError(e); // 异常结束
}
});
// 方法立即返回,连接保持打开
return emitter;
}
}
关键点:return emitter 之后请求线程就释放了,真正的推送发生在异步线程中,这也是 SSE 能"细水长流"的原因。
2. 前端接收(浏览器原生支持)
const source = new EventSource('/sse/stream');
// 监听指定事件名
source.addEventListener('message', (e) => {
console.log('收到:', e.data);
});
source.onerror = () => {
console.log('连接关闭或出错');
source.close();
};
无需任何第三方库,浏览器原生 EventSource 即可对接。
五、实战场景:AI 流式问答(打字机效果)
这是 SseEmitter 目前最火的应用场景。典型流程如下:
@PostMapping("/chat")
public SseEmitter chat(@RequestBody ChatRequest req) {
SseEmitter emitter = new SseEmitter(120_000L); // AI 生成较慢,超时设长一些
executor.execute(() -> {
try {
// 1. 参数校验失败时,推送 error 事件并结束
if (StringUtils.isBlank(req.getQuestion())) {
emitter.send(SseEmitter.event()
.name("error")
.data("问题不能为空,请重试!"));
emitter.complete();
return;
}
// 2. 调用大模型的流式接口,每收到一个 token 就转发给前端
llmClient.streamChat(req.getQuestion(), token -> {
try {
emitter.send(SseEmitter.event()
.name("data")
.data(token));
} catch (IOException e) {
emitter.completeWithError(e);
}
});
// 3. 生成结束,推送完成事件
emitter.send(SseEmitter.event().name("done").data("[DONE]"));
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
设计要点:
- 用事件名区分消息类型:如
data(正文)、error(错误提示)、done(结束标记),前端按事件名分别处理。 - 错误也通过 SSE 推送:连接已经建立,HTTP 状态码没法再改,所以业务错误要用
error事件告知前端,而不是抛异常。 - 超时时间要结合业务设置:AI 生成可能耗时较长,默认超时往往不够,建议定义常量统一管理。
六、SSE 与 WebSocket 怎么选?
| 对比项 | SSE (SseEmitter) | WebSocket |
|---|---|---|
| 通信方向 | 单向(服务端 → 客户端) | 双向 |
| 底层协议 | 普通 HTTP | 独立协议(ws:// / wss://) |
| 浏览器支持 | 原生 EventSource |
原生 WebSocket |
| 断线重连 | EventSource 自动重连 |
需自行实现 |
| 实现复杂度 | 简单,无需额外依赖 | 较重,需握手升级协议 |
| 典型场景 | AI 流式输出、进度条、消息通知 | 聊天室、实时协作、游戏 |
一句话总结:只需要服务端往客户端推,选 SSE,简单省事;需要双向实时通信,才上 WebSocket。
七、常见踩坑点
- 忘记异步:如果在 Controller 主线程里循环
send,会一直占用 Tomcat 工作线程,高并发下线程池被打满。务必将推送逻辑放到独立线程池。 - 超时未处理:默认超时后 Spring 会抛
AsyncRequestTimeoutException,记得通过onTimeout回调做清理,或按需调大超时时间。 - Nginx 缓冲:经过 Nginx 代理时,响应可能被缓冲导致"不流式"。需要在代理配置中关闭缓冲:
proxy_buffering off; proxy_cache off; proxy_set_header Connection ''; proxy_http_version 1.1; - 连接已断开仍在 send:客户端关闭页面后继续
send会抛IOException,捕获后应调用completeWithError并终止后续推送。 - EventSource 只支持 GET:浏览器原生
EventSource不支持 POST。若接口必须是 POST(如需要传大 JSON),前端可改用fetch+ReadableStream解析 SSE 格式。
八、总结
SseEmitter是 Spring MVC 实现 服务端单向流式推送 的核心类;- 核心用法:Controller 返回
SseEmitter,异步线程中send()推送、complete()结束; - 最典型的应用就是 AI 流式问答的打字机效果;
- 与 WebSocket 相比更轻量,单向推送场景首选 SSE。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注三连!有问题可以在评论区留言讨论~
更多推荐


所有评论(0)