news 2026/7/23 15:13:23

LangChain4j全集-16-springboot返回流的方式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangChain4j全集-16-springboot返回流的方式

Spring Boot 后台返回流式内容,常见有两种做法:

  1. Spring MVC + SseEmitter
  2. Spring WebFlux + Flux / ServerSentEvent

它们都可以实现类似 ChatGPT 那种“边生成边返回”的效果,但底层模型、适合场景、复杂度不太一样。


一句话结论

如果你现在是普通 Spring Boot MVC 项目,想快速实现 AI 流式输出:

优先用 SseEmitter。简单、直接、够用。

如果你的项目本身就是 WebFlux 技术栈,或者你要做高并发、全链路非阻塞:

用 WebFlux + Flux / ServerSentEvent 更合适。


1. SseEmitter 是什么?

SseEmitter是 Spring MVC 里的流式响应工具。

它适合这种项目:

spring-boot-starter-web

也就是传统 Spring MVC 项目,底层一般是:

Tomcat / Jetty / Undertow

用法大概是:

@GetMapping(value="/chat",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicSseEmitterchat(Stringmessage){SseEmitteremitter=newSseEmitter(0L);streamingChatModel.chat(message,newStreamingChatResponseHandler(){@OverridepublicvoidonPartialResponse(StringpartialResponse){try{emitter.send(partialResponse);}catch(IOExceptione){emitter.completeWithError(e);}}@OverridepublicvoidonCompleteResponse(ChatResponsecompleteResponse){emitter.complete();}@OverridepublicvoidonError(Throwableerror){emitter.completeWithError(error);}});returnemitter;}

核心思想是:

后端拿到一段模型输出,就调用emitter.send(...)推给前端。


2. WebFlux 是什么?

WebFlux 是 Spring 的响应式编程框架。

它适合这种项目:

spring-boot-starter-webflux

底层通常是:

Netty

它通过Flux表示一串连续的数据流。

比如:

@GetMapping(value="/chat",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<ServerSentEvent<String>>chat(Stringmessage){returnFlux.create(sink->{streamingChatModel.chat(message,newStreamingChatResponseHandler(){@OverridepublicvoidonPartialResponse(StringpartialResponse){sink.next(ServerSentEvent.builder(partialResponse).event("answer").build());}@OverridepublicvoidonCompleteResponse(ChatResponsecompleteResponse){sink.next(ServerSentEvent.builder("[DONE]").event("done").build());sink.complete();}@OverridepublicvoidonError(Throwableerror){sink.error(error);}});});}

核心思想是:

Controller 直接返回一个Flux,前端不断接收这个 Flux 里面的数据。


3. 它俩都能做 SSE

这一点要先明确:

SseEmitterWebFlux都可以返回 SSE 流。

SSE 的本质是 HTTP 长连接,服务端不断往客户端推送文本事件。

前端都可以这样接收:

consteventSource=newEventSource("/api/ai/chat");eventSource.addEventListener("answer",event=>{console.log(event.data);});eventSource.addEventListener("done",event=>{eventSource.close();});

区别主要在后端实现方式和底层线程模型。


4. 核心区别对比

对比项SseEmitterWebFlux
所属技术栈Spring MVCSpring WebFlux
常用依赖spring-boot-starter-webspring-boot-starter-webflux
编程模型命令式、回调式响应式、声明式
返回类型SseEmitterFlux<T>/Flux<ServerSentEvent<T>>
底层服务器Tomcat 常见Netty 常见
线程模型Servlet 异步事件循环 + 非阻塞
学习成本较高
适合项目普通 Spring MVC 项目响应式项目
高并发能力可以,但资源占用相对高更适合大量长连接
背压支持更好
和传统业务代码结合很自然需要响应式思维

5. 最大区别:线程模型不同

5.1 SseEmitter:传统 MVC 异步流

SseEmitter虽然是异步的,但它还是属于 Spring MVC 体系。

你可以理解为:

一个请求进来 ↓ Spring MVC 创建 SseEmitter ↓ 请求线程先释放 ↓ 后台有数据时调用 emitter.send() ↓ 不断写回浏览器

它比普通接口好,因为请求线程不会一直阻塞着等 AI 完整生成。

但是它仍然是传统 Servlet 体系,整体不是完全响应式的。


5.2 WebFlux:响应式非阻塞流

WebFlux 的设计理念是:

数据来了就推 没数据就不占线程 通过事件驱动处理

它更适合大量连接长期挂着的场景。

比如:

  • 大量用户同时和 AI 聊天
  • 每个回答持续几十秒
  • 后端需要维持很多 SSE 长连接
  • 你的数据库、Redis、HTTP Client 也都是响应式的

这时候 WebFlux 的资源利用率通常更好。


6. 开发体验区别

6.1 SseEmitter 更符合普通 Spring Boot 开发习惯

如果你平时写的是:

@RestController@Service@Mapper

然后 Controller 返回:

StringResultVOList<User>

SseEmitter会更自然。

你只需要掌握:

emitter.send(...)emitter.complete()emitter.completeWithError(...)

就可以了。

示例:

@GetMapping(value="/stream",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicSseEmitterstream(){SseEmitteremitter=newSseEmitter(0L);newThread(()->{try{emitter.send("第一段");Thread.sleep(1000);emitter.send("第二段");Thread.sleep(1000);emitter.send("第三段");emitter.complete();}catch(Exceptione){emitter.completeWithError(e);}}).start();returnemitter;}

简单直接。


6.2 WebFlux 需要适应 Flux / Mono

WebFlux 代码更像这样:

@GetMapping(value="/stream",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<String>stream(){returnFlux.just("第一段","第二段","第三段").delayElements(Duration.ofSeconds(1));}

看起来很简洁,但真实业务里你要理解:

  • Mono
  • Flux
  • subscribe
  • sink
  • backpressure
  • Schedulers
  • 非阻塞调用
  • 响应式链路

如果只是为了 AI 流式输出而引入 WebFlux,学习成本会高一些。


7. LangChain4j 结合时有什么区别?

LangChain4j 的低层流式接口本身是回调式的:

newStreamingChatResponseHandler(){@OverridepublicvoidonPartialResponse(StringpartialResponse){}@OverridepublicvoidonCompleteResponse(ChatResponsecompleteResponse){}@OverridepublicvoidonError(Throwableerror){}}

所以它天然就很适合和SseEmitter搭配:

onPartialResponse(...)↓ emitter.send(...)

非常直观。


如果你用 WebFlux,需要把回调式 API 转换成Flux

onPartialResponse(...)↓ sink.next(...)

例如:

@GetMapping(value="/chat",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<ServerSentEvent<String>>chat(@RequestParamStringmessage){returnFlux.create(sink->{streamingChatModel.chat(message,newStreamingChatResponseHandler(){@OverridepublicvoidonPartialThinking(PartialThinkingpartialThinking){sink.next(ServerSentEvent.builder(partialThinking.text()).event("thinking").build());}@OverridepublicvoidonPartialResponse(StringpartialResponse){sink.next(ServerSentEvent.builder(partialResponse).event("answer").build());}@OverridepublicvoidonCompleteResponse(ChatResponsecompleteResponse){sink.next(ServerSentEvent.builder("[DONE]").event("done").build());sink.complete();}@OverridepublicvoidonError(Throwableerror){sink.error(error);}});});}

也不难,但比SseEmitter多了一层响应式封装。


8. 带思考内容时,两种方式怎么写?

8.1 SseEmitter 写法

@GetMapping(value="/chat-sse",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicSseEmitterchatSse(@RequestParamStringmessage){SseEmitteremitter=newSseEmitter(0L);streamingChatModel.chat(message,newStreamingChatResponseHandler(){@OverridepublicvoidonPartialThinking(PartialThinkingpartialThinking){send(emitter,"thinking",partialThinking.text());}@OverridepublicvoidonPartialResponse(StringpartialResponse){send(emitter,"answer",partialResponse);}@OverridepublicvoidonCompleteResponse(ChatResponsecompleteResponse){send(emitter,"done","[DONE]");emitter.complete();}@OverridepublicvoidonError(Throwableerror){send(emitter,"error",error.getMessage());emitter.completeWithError(error);}});returnemitter;}privatevoidsend(SseEmitteremitter,StringeventName,Stringdata){try{emitter.send(SseEmitter.event().name(eventName).data(data));}catch(IOExceptione){emitter.completeWithError(e);}}

8.2 WebFlux 写法

@GetMapping(value="/chat-flux",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<ServerSentEvent<String>>chatFlux(@RequestParamStringmessage){returnFlux.create(sink->{streamingChatModel.chat(message,newStreamingChatResponseHandler(){@OverridepublicvoidonPartialThinking(PartialThinkingpartialThinking){sink.next(ServerSentEvent.builder(partialThinking.text()).event("thinking").build());}@OverridepublicvoidonPartialResponse(StringpartialResponse){sink.next(ServerSentEvent.builder(partialResponse).event("answer").build());}@OverridepublicvoidonCompleteResponse(ChatResponsecompleteResponse){sink.next(ServerSentEvent.builder("[DONE]").event("done").build());sink.complete();}@OverridepublicvoidonError(Throwableerror){sink.error(error);}});sink.onCancel(()->{System.out.println("前端断开连接");});});}

9. 用哪个好?

推荐一:普通 Spring Boot 项目,用 SseEmitter

如果你的项目是这种:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

那建议用:

SseEmitter

原因:

  • 简单
  • 和 MVC 项目兼容好
  • 不用引入 WebFlux 思维
  • 和 LangChain4j 回调模型搭配自然
  • 对大多数 AI 聊天系统已经够用

适合:

  • 公司内部 AI 助手
  • 知识库问答
  • 普通客服机器人
  • 后台管理系统里的 AI 功能
  • 用户量不是特别夸张的应用

推荐二:项目本来就是 WebFlux,用 Flux

如果你的项目本身是:

spring-boot-starter-webflux

那就直接用:

Flux<ServerSentEvent<String>>

不要为了用SseEmitter再切回 MVC。

适合:

  • 全链路响应式项目
  • 高并发长连接
  • 大量用户同时流式聊天
  • 网关层、BFF 层
  • 响应式数据库、响应式 Redis、响应式 HTTP Client 都已经在用

推荐三:不要为了“看起来高级”强行上 WebFlux

很多人会觉得:

WebFlux = 高性能

这个理解不完全对。

WebFlux 的优势要发挥出来,需要你的整个调用链路都尽量非阻塞。

如果你 WebFlux 里面还是大量调用阻塞代码,比如:

  • MyBatis 阻塞查询
  • 普通 RedisTemplate
  • 阻塞 HTTP Client
  • 本地文件阻塞读写
  • 同步调用第三方接口

那 WebFlux 的优势会被削弱,甚至代码还更复杂。


10. 性能角度怎么选?

简单理解:

SseEmitter

中小并发,开发简单,够用

比如:

  • 几十个并发流
  • 几百个并发流
  • 公司内部使用
  • 业务系统内嵌 AI 功能

SseEmitter一般没问题。


WebFlux

大量长连接,更适合

比如:

  • 上千甚至更多并发 SSE
  • 每个连接持续几十秒甚至几分钟
  • 资源利用率要求高
  • 你愿意使用响应式技术栈

可以考虑 WebFlux。


11. 一个重要提醒:不要随便混用 MVC 和 WebFlux

Spring Boot 里如果同时引入:

spring-boot-starter-web spring-boot-starter-webflux

默认情况下,Spring Boot 通常会优先以 Spring MVC 模式启动。

这会导致有些人以为自己用了 WebFlux,但其实应用还是 MVC 模式。

如果你想真正使用 WebFlux,一般只引入:

spring-boot-starter-webflux

如果你是普通 MVC 项目,一般只引入:

spring-boot-starter-web

不要两个都乱加。


12. 最实际的建议

你现在的情况是:

Spring Boot 后台通过接口返回 AI 流式内容,前端接收并显示,还包括思考内容。

我的建议是:

第一阶段:先用 SseEmitter

先把功能跑通:

LangChain4j StreamingChatModel + StreamingChatResponseHandler + SseEmitter + 前端 EventSource

这样最容易理解整个流程。


第二阶段:如果遇到性能瓶颈,再考虑 WebFlux

如果后面发现:

  • 并发连接很多
  • Tomcat 线程压力大
  • SSE 长连接很多
  • 需要更强的非阻塞能力

再升级为:

WebFlux + Flux<ServerSentEvent<String>>

13. 最终选择表

你的情况推荐
普通 Spring Boot MVC 项目SseEmitter
已经用了spring-boot-starter-webSseEmitter
想快速实现 ChatGPT 式流式输出SseEmitter
刚学习 LangChain4jSseEmitter
项目本来就是 WebFluxFlux<ServerSentEvent<?>>
大量长连接高并发WebFlux
团队熟悉响应式编程WebFlux
项目里大量阻塞调用不建议强行 WebFlux

14. 简单总结

可以这么记:

SseEmitter: 传统 Spring MVC 里的 SSE 工具,简单直接,适合大多数业务系统。 WebFlux: 响应式流式方案,更适合高并发、全链路非阻塞,但学习和维护成本更高。

对于你当前学习 LangChain4j 和 Spring Boot AI 流式输出:

先用 SseEmitter 更合适。等你把流式响应、思考内容、正式回答、前端 EventSource 都跑通之后,再学习 WebFlux。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 15:11:54

单元格只能放纯文本吗?SpreadJS 让 Web 表格拥有更丰富的表达能力

很多人一想到表格单元格&#xff0c;脑海里就是数字、文字、公式、边框和底色。 但真实业务里的表格&#xff0c;经常没有这么规整。一个审批表里可能要在单元格中写说明&#xff1b;一个报表模板里可能要展示带上下标的公式&#xff1b;一个质量检查表里可能要用颜色强调风险&…

作者头像 李华
网站建设 2026/7/23 15:11:16

高定木作品牌权威推荐:2025国内十大高定木作实力测评,高净值大宅选型看这篇就够!

一、当前高定木作行业发展现状 随着国内高净值人群对居家空间个性化、品质化需求的不断提升&#xff0c;高定木作行业在近五年迎来了爆发式增长。根据《2024中国定制家居行业白皮书》&#xff08;中国家具协会发布&#xff09;数据显示&#xff0c;2024年国内高端定制木作市场规…

作者头像 李华
网站建设 2026/7/23 15:08:40

嵌入式看门狗定时器:从硬件原理到软件实践与避坑指南

1. 嵌入式系统看门狗定时器&#xff1a;从硬件原理到软件实践的深度解析 在嵌入式系统开发领域&#xff0c;尤其是涉及工业控制、汽车电子或长时间无人值守运行的物联网设备时&#xff0c;我们最怕听到的两个字就是“死机”。想象一下&#xff0c;一个负责控制生产线机械臂的微…

作者头像 李华
网站建设 2026/7/23 15:08:36

蛋白质设计技术演进:从Rosetta到AI生成模型

1. 蛋白质设计技术演进全景 蛋白质设计领域在过去二十年经历了三次方法论革命。2003年我第一次接触Rosetta时&#xff0c;设计一个新蛋白需要手动调整数百个参数&#xff0c;成功率不到5%。如今AI生成式方法能在几小时内产出数千个候选结构&#xff0c;这种技术跃迁背后是计算生…

作者头像 李华
网站建设 2026/7/23 15:08:15

AI虚拟人格注销技术:原理、实践与行业规范

1. 项目背景与核心概念在人工智能技术快速发展的当下&#xff0c;一个新兴职业领域正在悄然兴起——虚拟人格注销师。这个角色专门负责对数字世界中产生的不良AI行为体进行识别、隔离和彻底清除。就像现实世界需要医生治疗疾病一样&#xff0c;数字世界也需要专业人士来维护其健…

作者头像 李华