流式编程及Spring中SSE实现

目录

[1. 流式编程](#1. 流式编程)

[1.1 SSE协议介绍](#1.1 SSE协议介绍)

[1.2 SSE数据格式](#1.2 SSE数据格式)

[2. Spring中SSE实现](#2. Spring中SSE实现)

[2.1 未设置retry同步发送响应](#2.1 未设置retry同步发送响应)

[2.2 设置retry同步发送响应](#2.2 设置retry同步发送响应)

[2.3 自定义事件](#2.3 自定义事件)

[2.3.1 终止连接](#2.3.1 终止连接)

[2.4 SSE与流式编程结合应用](#2.4 SSE与流式编程结合应用)

[2.4.1 Flux的概念](#2.4.1 Flux的概念)

[2.4.2 Flux的快速使用](#2.4.2 Flux的快速使用)

使用Flux:

常见操作符:

坑点1:

Flux实现前后端交互:

坑点2:


1. 流式编程

1.1 SSE协议介绍

先回想HTTP协议。HTTP协议本身是无状态的请求-响应模式,也就是说服务端 不会保存任何客户端 请求历史或身份状态,当服务端处理完一次请求后,就会彻底忘掉这个客户端。因此服务端无法主动向客户端推送数据。

SSE是基于HTTP协议的一种轻量级实时通信协议。它可实现流式传输,允许服务器主动向客户端(浏览器)推送数据。 (Server-Sent Events 服务器发送事件,简称SSE)

SSE协议核心特点:

  • 基于HTTP协议

复用标准HTTP/HTTPS协议,无需额外端口或协议,兼容性好易于部署

  • 单向通信机制

SSE仅支持服务端向客户端的单向数据推送。客户端先通过HTTP请求建立连接后,服务器可持续发送数据流,但客户端无法通过同一连接向服务器发送数据。

  • 自动重连机制

支持断线重连,连接中断时,浏览器会自动尝试重新连接(支持retry字段自定义重连间隔)

  • 自定义消息类型

客户端发起请求后,服务器要想保持连接开放,响应头设置为Content-Type: text/event-stream ,标识为事件流格式,持续推送事件流。

HTTP和SSE:

1.2 SSE数据格式

服务端向客户端(浏览器)发送SSE数据需设置响应头信息。

Content-Type: text/event-stream;charset=utf-8


每一次发送的信息,由若干个message组成,每个message之间由\n\n分隔,每个message内部由若干行组成,每一行都是如下格式

field: value\n

Field 可以取值为:

  • data(必需):数据内容
  • event(非必需):表示自定义的事件类型,默认是message事件
  • id(非必需):数据标识符,相当于每一条数据的编号
  • retry(非必需):指定浏览器重新发起连接的时间间隔

除此之外,还可以有冒号 **:**开头的行,表示注解。

数据示例:

: 以\n\n分隔message
event: foo\n //foo事件,事件名可以自定义
data: a foo event\n\n
data: an unnamed event\n\n //默认为message事件
event: end\n //自定义的end事件
data: a bar event\n\n


notice :message是通过一个或多个键值对拼接而成,每个message以\n\n结尾分隔,后端发送的响应可包含多个message,而message事件是默认的事件类型。

流式编程只通过文字阐述,很难表达清楚,下面我将通过Java前后端交互体现。


2. Spring中SSE实现

2.1 未设置retry同步发送响应

(代码在git:spring-ai-first-term-practice · 巨蛇神/JavaEE进阶 - 码云 - 开源中国)

  1. 需要编写前端代码,下面是代码前端解读。
  2. new EventSource(url),就是建立并维护一条到服务端的 SSE 长连接,自动接收服务端推送过来的消息。
  3. eventSource.onmessage = function(event){...},onmessage是专门用来监听message事件的,其他事件监听不到,如果后端向前端发送的信息中含有message事件的message,就执行function函数,且该函数的形参接收的实参为整个message。
  4. document.getElementById("sse").innerHTML=event.data;利用形参取出data:并赋值给给id名为sse的标签。

浏览器访问index.html,可以看到连续打印10次时间,然后间隔几秒又自动向后端发送请求,尝试重连。

2.2 设置retry同步发送响应

通过fiddle抓包:

2.3 自定义事件

message类型有两类:

|------------|--------------------|--------------------------------------|
| 消息类型 | 服务端格式 | 前端监听方式 |
| 默认消息‌ | 只有 data:,没有 event: | onmessage 能收到 |
| ‌自定义事件 | 有 event: 事件名 | onmessage ‌收不到‌,必须用 addEventListener |


2.3.1 终止连接

后端把有效数据都发给前端后,服务器最后发一个响应告诉浏览器此次连接终止,连接关闭,浏览器不会尝试重连。

2.4 SSE与流式编程结合应用

从Spring 5(Spring Boot 5)开始可以使用WebFlux实现SSE。Flux是WebFlux的核心API。

2.4.1 Flux的概念

  • 可以把Flux想象成一条传送带
  1. 传送带上有许多数据,把数据看成物品
  2. 我们可以对物品进行加工,比如:过滤、修改(修改数据)
  3. 物品逐个的到达,不用等待全部到齐(也就是支持异步)
  4. 对端,也就是接收物品的一方可以调速(也就是背压机制)

2.4.2 Flux的快速使用

Flux的流程分为三个步骤:

1.创建Flux

创建一个Flux数据流,并有数据源。

2.处理数据

使用操作符对数据进行处理。

3.订阅数据

订阅Flux来消费数据,触发数据的流动。不订阅(消费数据),数据无法流动,你就取不到任何数据。


使用Flux:

notice:

创建一个Flux流,不管Flux内做了什么加工操作(.map或.delayElements等),这个Flux就只是一张"数据蓝图",只有订阅数据,Flux才会活起来,才会输出数据。

上图用Flux.just()创建包含指定元素的Flux流,还可用Flux.formIterable()和Flux.range()等。

// 从集合 ( 如 List 、 Set) 创建
Flux. fromIterable (Arrays. asList ( 1 , 2 , 3 ))
// ⽣成 1~5 的 Flux
Flux.range( 1 , 5 )


常见操作符:

|---------------|------------------|------------------------------------------|
| 操作 | 作用 | 示例代码片段 |
| map() | 元素一对一转换 | .map(String::toUpperCase) |
| filter() | 条件过滤 | .filter(s->s.length()>5) //筛选出长度大于5的 |
| take() | 限制元素数量 | .take(2) //只取前两个元素 |
| merge() | 合并多个Flux(不保证顺序) | Flux.merge(Flux.just("A"),Flux.just("B)) |
| concat() | 顺序拼接多个Flux(保证顺序) | Flux.merge(Flux.just("A"),Flux.just("B)) |
| delayElements | 延迟元素发射 | .delayElements(Duration.ofseconds(1)) |

坑点1:

Flux实现前后端交互:
坑点2:

看了"Flux实现前后端交互 "的代码,你可能产生疑惑:"Flux使用流程包含1.创建含有数据源的Flux流,2.处理数据,3.订阅数据",而"**Flux实现前后端交互"**中没使用函数进行订阅,前端还能正常获取Flux流中的数据,现在你百思不得其解。

其实这里是伟大的Spring WebFlux框架在自动帮你订阅。只有在controller层函数返回的Flux会被框架订阅,其他任何地方都不会被管理。

相关推荐
Flynn Liang32 分钟前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
Gopher_HBo34 分钟前
Go CPU 剖析的"地基":信号、CAS、写屏障、无锁环形缓冲
后端
panbook1 小时前
从0到1实现一个分布式时序数据库(二):写入成功之后,如何用 WAL 找回数据
后端
Swot1 小时前
上传一张 4MB 图片,我先后撞上了三层请求体限制
后端
聪明的笨猪猪1 小时前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
IT_陈寒1 小时前
Vite的HMR把我整不会了:一个生产环境的热更新坑
前端·人工智能·后端
OLRainM1 小时前
实习复盘:大模型网关渠道质量评分实战(二)—— 报错了到底算谁的锅?流式归因与 Token 补账踩过的 8 个真坑
后端
愿有所息1 小时前
记一次 ARM64 + JDK8 上 fastjson2 的诡异数据错乱
java
梦想恒远2 小时前
Spring Boot + LangChain4j 统计 token 成本:ChatModelListener 实测与单价口径
spring boot·后端
秋饼2 小时前
MCP 2026-07-28 无状态协议大改版:Spring AI 2.1 多轮往返请求与网关路由落地
java·ai·技术分享·后端开发