使用Spring Boot实现Server-Sent Events(SSE)实时推送

使用Spring Boot实现Server-Sent Events(SSE)实时推送

什么是SSE?

Server-Sent Events(SSE) 是一种基于HTTP的服务器向客户端单向实时推送数据的技术。与WebSocket不同,SSE仅支持服务器到客户端的单向通信,适用于实时通知、股票行情、新闻推送等场景。

核心特性:

  • 基于HTTP协议,兼容现有基础设施
  • 支持自动重连
  • 轻量级,使用简单
  • 默认支持文本数据格式

一、快速搭建Spring Boot SSE服务

1. 创建项目

通过Spring Initializr创建项目,选择依赖:

  • Spring Web(用于Web端点)

2. 实现SSE控制器

java 复制代码
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.servlet.mvc.method.annotation.SseEmitter;

@RestController
public class SSEController {

    private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();

    // 客户端连接端点
    @GetMapping(path = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter connect(@RequestParam String userId) {
        SseEmitter emitter = new SseEmitter(60_000L); // 超时时间60秒
        
        // 注册清理回调
        emitter.onCompletion(() -> emitters.remove(userId));
        emitter.onTimeout(() -> emitters.remove(userId));
        
        emitters.put(userId, emitter);
        return emitter;
    }

    // 消息推送方法(可在Service中调用)
    public void sendEvent(String userId, String message) {
        SseEmitter emitter = emitters.get(userId);
        if (emitter != null) {
            try {
                emitter.send(SseEmitter.event()
                        .data(message)
                        .name("custom-event")); // 自定义事件名称
            } catch (IOException e) {
                emitters.remove(userId);
            }
        }
    }
}

二、客户端实现

HTML页面示例:

html 复制代码
<!DOCTYPE html>
<html>
<body>
    <div id="messages"></div>
    <script>
        const eventSource = new EventSource('http://localhost:8080/connect?userId=123');
        
        // 监听默认消息
        eventSource.onmessage = (e) => {
            document.getElementById('messages').innerHTML += e.data + '<br>';
        };

        // 监听自定义事件
        eventSource.addEventListener('custom-event', (e) => {
            console.log('Custom event:', e.data);
        });
    </script>
</body>
</html>

三、进阶功能实现

1. 广播消息

java 复制代码
public void broadcast(String message) {
    emitters.forEach((id, emitter) -> {
        try {
            emitter.send(message);
        } catch (IOException e) {
            emitters.remove(id);
        }
    });
}

2. 发送结构化数据(JSON)

java 复制代码
emitter.send(SseEmitter.event()
        .data(new MyDataObject("value"), MediaType.APPLICATION_JSON)
        .id("event-123")
        .reconnectTime(5000));

3. 心跳机制(防止连接超时)

java 复制代码
@Scheduled(fixedRate = 30_000)
public void sendHeartbeat() {
    emitters.forEach((id, emitter) -> {
        try {
            emitter.send(SseEmitter.event().comment("heartbeat"));
        } catch (IOException ignored) {}
    });
}

四、注意事项

  1. 连接管理

    • 使用ConcurrentHashMap存储连接
    • 及时移除已关闭的连接
    • 设置合理的超时时间(默认:30秒)
  2. 跨域问题

    java 复制代码
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/connect")
                    .allowedOrigins("*");
        }
    }
  3. 性能优化

    • 调整Tomcat线程池配置
    • 考虑使用异步处理(@Async

五、SSE vs WebSocket

特性 SSE WebSocket
协议 HTTP WS/TCP
通信方向 单向(服务器→客户端) 双向
数据格式 Text/Event-Stream 二进制/自定义
自动重连 支持 需手动实现
浏览器兼容性 除IE外主流浏览器 现代浏览器

总结

SSE是实现服务器推送的高效解决方案,适用于需要实时更新但不需要双向通信的场景。结合Spring Boot的SseEmitter,开发者可以快速构建实时功能。完整示例代码已上传至GitHub仓库

扩展阅读:


通过本文,读者可以快速掌握SSE的核心实现,并根据实际需求扩展功能。建议根据具体场景选择合适的实时通信方案。

相关推荐
Henry2you13 分钟前
新手引导-纯js手搓
前端
小桥风满袖13 分钟前
Three.js-硬要自学系列13 (加载gltf外部模型、加载大模型)
前端·css·three.js
李菠菜14 分钟前
SpringBoot项目中策略模式与简单工厂、模板方法的优雅融合实践
spring boot·后端·设计模式
前端涂涂14 分钟前
express-generratior工具用法
前端·后端
正在努力的前端小白14 分钟前
Vue3可编辑字段组件的演进之路:从繁琐到优雅
前端·javascript·vue.js
李菠菜21 分钟前
SpringBoot中MongoDB大数据量查询慢因实体映射性能瓶颈优化
spring boot·后端·mongodb
yeyong26 分钟前
python3中的 async 与 await关键字,实现异步编程
后端
倚栏听风雨28 分钟前
spring boot 实现MCP server
后端
yeyong28 分钟前
在 Docker 中安装 Playwright 时遇到 RuntimeError: can't start new thread 错误
后端
极客小俊1 小时前
粘性定位Position:sticky属性是不是真的没用?
前端