Java 中“流式输出”可以理解为 边处理边发送数据,而不是一次性构建完整内容再发送,这在以下场景中非常常见:
- Web 开发中:服务端向前端发送 Server-Sent Events(SSE)
- 命令行中:处理大型数据文件,逐行输出
- 与 OpenAI API、搜索引擎等增量响应接口对接
下面我们从几个角度介绍 Java 实现流式输出的方式:
✅ 一、控制台中的流式输出
示例:边读取边输出
import java.io.*;
public class StreamOutputExample {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("large.txt"));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
// 模拟处理延迟
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
reader.close();
}
}
这种方式适用于处理大文本、日志、传感器流等。
✅ 二、Java Web 中的流式输出(Servlet)
示例:原始 Servlet 中通过 HttpServletResponse
实现流式输出:
@WebServlet("/stream")
public class StreamServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain;charset=UTF-8");
PrintWriter out = resp.getWriter();
for (int i = 1; i <= 10; i++) {
out.println("第 " + i + " 行");
out.flush(); // 🔴关键:刷新缓冲区
try { Thread.sleep(500); } catch (InterruptedException e) {}
}
out.close();
}
}
访问 /stream
会看到逐行输出的内容。
✅ 三、Spring Boot 中实现流式输出(推荐方式)
示例:返回一个 ResponseBody
的 StreamingResponseBody
@RestController
public class StreamController {
@GetMapping("/stream")
public ResponseEntity<StreamingResponseBody> streamText() {
StreamingResponseBody stream = outputStream -> {
PrintWriter writer = new PrintWriter(outputStream);
for (int i = 1; i <= 10; i++) {
writer.println("数据 " + i);
writer.flush();
Thread.sleep(500); // 模拟延迟
}
};
return ResponseEntity
.ok()
.contentType(MediaType.TEXT_PLAIN)
.body(stream);
}
}
访问 /stream
会看到类似“直播”输出的效果。
✅ 四、使用 Server-Sent Events(SSE) 实现前后端实时通信
Spring Boot 中流式响应 SSE:
@RestController
public class SseController {
@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter stream() {
SseEmitter emitter = new SseEmitter();
new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
emitter.send(SseEmitter.event()
.data("第 " + i + " 条消息")
.id(String.valueOf(i))
.name("message"));
Thread.sleep(1000);
}
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
}).start();
return emitter;
}
}
前端配合:
<script>
const sse = new EventSource('/sse');
sse.onmessage = function(event) {
console.log("收到消息:", event.data);
}
</script>
✅ 五、配合 OpenAI 接口返回流式内容(chat/completions)
你可以使用 HttpClient
或 WebClient
发送带 "stream": true
的请求,然后逐行解析 OpenAI 返回的 SSE 数据。
🔚 总结
场景 | 实现方式 |
---|---|
控制台/命令行 | System.out + 手动 flush |
Servlet | HttpServletResponse.getWriter() + flush() |
Spring Boot REST | StreamingResponseBody |
SSE 实时通信 | SseEmitter |
与 OpenAI SSE 接口交互 | WebClient + 流式解析 |
好的,那我们就继续 Java + Spring Boot 中使用 SSE(Server-Sent Events)流式调用 OpenAI 接口的完整实现过程,包括:
✅ 前提条件、
✅ OpenAI 接口设置 stream=true
、
✅ 后端 Java 用 WebClient
或 OkHttp
实现 SSE 解析,
✅ 使用 SseEmitter
将数据一条条推送给前端页面。
✅ 使用场景
前端想“边生成边显示”OpenAI 返回的回答(比如 ChatGPT 效果);
后端要中转 OpenAI 的 SSE 响应,然后以 SSE 的形式再发给前端。
🧱 1. Maven 依赖<dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- WebClient 用于调用 OpenAI 接口 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> </dependencies>
📡 2. Spring Boot Controller 实现 SSE 推送@RestController @RequestMapping("/chat") public class ChatController { private final WebClient webClient; public ChatController(WebClient.Builder builder) { this.webClient = builder.baseUrl("https://api.openai.com").build(); } @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(@RequestParam String prompt) { SseEmitter emitter = new SseEmitter(0L); // 不限制时间 // 构造请求体 Map<String, Object> request = new HashMap<>(); request.put("model", "gpt-3.5-turbo"); request.put("stream", true); request.put("messages", List.of(Map.of("role", "user", "content", prompt))); webClient.post() .uri("/v1/chat/completions") .header(HttpHeaders.AUTHORIZATION, "Bearer YOUR_OPENAI_API_KEY") .contentType(MediaType.APPLICATION_JSON) .bodyValue(request) .retrieve() .bodyToFlux(String.class) .subscribe( line -> { try { // OpenAI 使用的是 text/event-stream 格式,处理 "data: ..." if (line.startsWith("data: ")) { String json = line.substring(6).trim(); if ("[DONE]".equals(json)) { emitter.complete(); return; } // 解析 delta 内容 ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(json); String content = root .path("choices").get(0) .path("delta").path("content").asText(); if (!content.isEmpty()) { emitter.send(SseEmitter.event().data(content)); } } } catch (Exception e) { emitter.completeWithError(e); } }, error -> emitter.completeWithError(error), emitter::complete ); return emitter; } }
🖥️ 3. 前端页面接收 SSE(简单 HTML 示例)<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SSE Chat</title> </head> <body> <h2>AI回答:</h2> <div id="response"></div> <script> const source = new EventSource("/chat/stream?prompt=你好,介绍一下SSE技术"); source.onmessage = function(event) { document.getElementById("response").innerHTML += event.data; }; source.onerror = function(err) { console.error("SSE 错误", err); source.close(); }; </script> </body> </html>
🎯 效果
打开页面后,OpenAI 会逐段返回内容,Java 后端边接收边推送给前端,用户会看到一段一段地生成文本,仿佛 ChatGPT 一样。
✅ 你可以自定义的部分
选择 GPT-4、GPT-3.5 模型;
修改 SseEmitter
推送内容格式,支持 Markdown、HTML;
添加 Redis 缓存、上下文管理等;
前端可用 Vue/React 组件来包裹。
发表回复