使用Spring SseEmitter实现服务端推送的示例代码

 更新时间:2026年02月28日 08:41:17   作者:gs80140  
SseEmitter 是 Spring MVC 4.2+ 引入的一个类,专门用于实现 Server-Sent Events (SSE),相比于 WebSocket,SSE 更轻量(基于 HTTP 协议),且支持自动重连,所以本文给大家介绍了如何使用Spring SseEmitter实现服务端推送,需要的朋友可以参考下

SseEmitter 是 Spring MVC 4.2+ 引入的一个类,专门用于实现 Server-Sent Events (SSE)。简单来说,SSE 允许服务器在建立连接后,主动向客户端推送数据,而不需要客户端反复轮询。

相比于 WebSocket,SSE 更轻量(基于 HTTP 协议),且支持自动重连。它非常适合像实时通知、股价更新、大模型(LLM)流式输出这类单向推送场景。

核心原理解析

在传统的 HTTP 请求中,客户端发送请求,服务器返回响应,然后连接关闭。而 SseEmitter 改变了这种模式:

  1. 保持连接:服务器返回的响应头包含 Content-Type: text/event-stream
  2. 分块传输:连接保持打开状态,服务器可以多次调用 emitter.send() 发送数据包。
  3. 结束生命周期:手动调用 complete() 或因为超时/错误触发 onTimeout/onError

前言

在实时性需求日益增长的今天,我们不一定非要动用“重型武器” WebSocket。如果你只需要服务器向客户端推送消息(如:进度条更新、ChatCompletion 响应),Spring 提供的 SseEmitter 可能是你的最佳选择。

一、 核心代码实现

使用 SseEmitter 通常分为三步:创建连接、保存引用、推送消息。

1. 控制器层 (Controller)

这是建立连接的入口。

@RestController
@RequestMapping("/api/sse")
public class SseController {

    // 用于存储已连接的客户端,实际生产中建议使用专门的服务类管理
    private static final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();

    @GetMapping(value = "/subscribe/{userId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter subscribe(@PathVariable String userId) {
        // 设置超时时间(单位毫秒),0 表示永不超时
        SseEmitter emitter = new SseEmitter(60_000L); 

        emitters.put(userId, emitter);

        // 注册回调
        emitter.onCompletion(() -> emitters.remove(userId));
        emitter.onTimeout(() -> emitters.remove(userId));
        emitter.onError((e) -> emitters.remove(userId));

        // 建立连接后立即发送一条消息,防止某些代理或浏览器认为连接已失效
        try {
            emitter.send(SseEmitter.event().name("INIT").data("Connected!"));
        } catch (IOException e) {
            emitter.completeWithError(e);
        }

        return emitter;
    }
}

2. 消息推送逻辑

你可以从任何异步线程调用 send 方法。

public void sendMessage(String userId, String content) {
    SseEmitter emitter = emitters.get(userId);
    if (emitter != null) {
        try {
            // 可以发送纯文本,也可以发送封装好的对象(会自动转 JSON)
            emitter.send(SseEmitter.event()
                    .id(UUID.randomUUID().toString())
                    .name("message") // 对应前端的事件名
                    .data(content)
                    .reconnectTime(3000)); // 提示客户端重连间隔
        } catch (IOException e) {
            emitters.remove(userId);
            emitter.completeWithError(e);
        }
    }
}

二、 前端如何接收?

浏览器内置了 EventSource API,调用非常简单:

const eventSource = new EventSource('/api/sse/subscribe/user123');

eventSource.onmessage = (event) => {
    console.log("收到通用消息:", event.data);
};

// 监听特定类型的事件 (对应后端的 .name("message"))
eventSource.addEventListener('message', (event) => {
    const data = JSON.parse(event.data);
    console.log("处理后的数据:", data);
});

eventSource.onerror = (error) => {
    console.error("SSE 错误:", error);
    eventSource.close();
};

三、 避坑指南(关键技巧)

  1. Nginx 配置:如果你使用了 Nginx 代理,必须配置 proxy_buffering off;proxy_set_header Connection "";,否则 Nginx 会缓存消息直到缓冲区满才一次性发给前端。
  2. 容器线程限制:虽然 SseEmitter 是异步的,但它依然占用一个 HTTP 连接。在高并发场景下,需要调整 Web 容器(如 Tomcat)的最大连接数。
  3. 超时处理:浏览器默认会在 SSE 断开后自动重连。在 onTimeout 中,务必调用 emitter.complete() 来清理服务器资源。
  4. 跨域问题:确保 CORS 配置允许 Last-Event-ID 等特殊请求头。

总结

SseEmitter 是 Spring 为我们封装的一把“轻巧的瑞士军刀”。它规避了 WebSocket 复杂的握手和协议转换,在处理流式输出(如 AI 对话响应)时表现极佳。

以上就是使用Spring SseEmitter实现服务端推送的示例代码的详细内容,更多关于Spring SseEmitter服务端推送的资料请关注脚本之家其它相关文章!

相关文章

  • IntelliJ IDEA社区版2021.3配置SpringBoot项目详细教程及失败案例

    IntelliJ IDEA社区版2021.3配置SpringBoot项目详细教程及失败案例

    IntelliJ IDEA 2021.3.3是一款集成开发环境,用于Java和其他编程语言的开发,下面这篇文章主要给大家介绍了关于IntelliJ IDEA社区版2021.3配置SpringBoot项目详细教程及失败案例的相关资料,需要的朋友可以参考下
    2024-03-03
  • idea本地jar使用maven打包本地依赖实现自动编译到项目里的操作

    idea本地jar使用maven打包本地依赖实现自动编译到项目里的操作

    这篇文章主要介绍了idea本地jar使用maven打包本地依赖实现自动编译到项目里的操作,本文通过示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-05-05
  • Java连接SQL Server数据库的超详细教程

    Java连接SQL Server数据库的超详细教程

    在Java应用程序中我们经常需要与数据库进行交互,一种常见的数据库是Microsoft SQL Server,下面这篇文章主要给大家介绍了关于Java连接SQL Server数据库的超详细教程,需要的朋友可以参考下
    2024-01-01
  • Java中equals和hashcode用法

    Java中equals和hashcode用法

    `equals`和`hashCode`方法在Java中密切相关,必须保持一致性,如果两个对象通过`equals`方法相等,它们的`hashCode`也必须相同,这对于基于哈希的数据结构至关重要,因为这些结构依赖哈希值进行快速查找和存储,为了减少哈希冲突
    2025-01-01
  • SpringBoot中的手动提交事务

    SpringBoot中的手动提交事务

    在Spring框架中使用@Transactional注解通常管理事务,但在多线程环境下此方法失效,本文讨论了手动事务的必要性及其实现方式,探讨了Spring的七种事务传播行为和数据库的四大特性与隔离级别,了解这些可以帮助开发者在无法使用声明式事务时
    2024-09-09
  • @Valid和@Validated注解校验以及异常处理方式

    @Valid和@Validated注解校验以及异常处理方式

    在Javaweb开发中,防止数据库恶意攻击是至关重要的,尽管前端校验可以起到一定的筛选作用,但通过工具如postman直接对后端发起请求的情况仍然需要后端进行严格的数据校验,Java生态下,@Valid注解配合SpringBoot提供了一个便捷高效的后端数据校验方案
    2024-11-11
  • Java中的synchronized有几种加锁方式(实例详解)

    Java中的synchronized有几种加锁方式(实例详解)

    在Java中,synchronized关键字提供了内置的支持来实现同步访问共享资源,以避免并发问题,这篇文章主要介绍了java的synchronized有几种加锁方式,需要的朋友可以参考下
    2024-05-05
  • IDEA 2020.1.1好用的plugins插件推荐

    IDEA 2020.1.1好用的plugins插件推荐

    这篇文章主要介绍了IDEA 2020.1.1好用的plugins插件推荐,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • Java中的byte & 0xff到底有什么作用?

    Java中的byte & 0xff到底有什么作用?

    这篇文章主要介绍了Java中的byte & 0xff到底有什么作用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Freemarker常用指令使用示例

    Freemarker常用指令使用示例

    这篇文章主要介绍了Freemarker常用指令使用示例,步骤简单,大家参考使用吧
    2013-11-11

最新评论