这几年只要聊到 AIJava 工程师基本都会陷入一种自我怀疑大模型时代Java 是不是正在边缘化招聘平台上的 AI 岗位动辄要求 Python团队内部做 AI 原型也默认用 Python再加上各类 AI 编程工具不断冲击“写业务代码”这件事本身很多后端同学心里都会打鼓——技术栈是不是选错了。但真等一个 AI 功能要上线、要扛流量、要对接企业现有系统时大家又会发现生产环境里的主力语言还是 Java。企业级的权限体系、交易链路、数据一致性、监控告警依然长在 Java 生态里。这意味着一个很关键的事实Java 做 AI 应用开发重心不是训练模型而是承接模型能力的应用层工程——接入、编排、流式输出、Agent、RAG、治理。这篇文章要解决的就是 Java 工程师在 AI 时代最实际的三个问题技术栈到底怎么选、SSE 流式输出这类交互怎么写、从零开始的学习路线应该怎么规划。无论你是刚入行的 Java 后端还是被安排接手 AI 功能的业务开发读完都能拿到一条可落地的路径。1. Java AI 应用开发解决的是“工程化”问题先说清楚一个概念边界AI 开发至少可以分成三层——算法层、应用层、基础设施层。算法层的工作是训练和微调模型需要扎实的深度学习功底Python 在这个场景下几乎没有对手。应用层的工作则是“把现成大模型的 API 能力接进业务系统”让它能回答你的私有知识库、能调用你内部的服务、能在一个聊天窗口里完成一次真实的业务操作。基础设施层包括 GPU 资源管理、推理服务部署、模型网关等这里反而是 Go 和 Java 的天下。Java 工程师真正能切入、也应该切入的是应用层。接下来看一个很多团队都会遇到的场景产品经理提了一个需求要在系统里做一个“智能客服”用户提问后需要逐字生成回答。Python 团队两天就能把 demo 跑起来但真到上线问题开始浮现——怎么做到每秒几千请求的并发接入怎么兼容公司现有的统一鉴权怎么把回答记录和工单系统、数据仓库打通怎么保证同一用户的重试请求不会重复扣费这些问题不再是“模型效果”的问题而是工程问题。Java 在高并发、事务处理、监控治理上的积累恰好能接住。所以结论很直接Java 工程师不需要焦虑要不要放弃 Java 去学 Python真正该做的是补齐大模型应用开发的技能缺口。2. Java AI 应用开发的技术栈分层在选型之前先建立一张总览图。一个完整的 Java AI 应用大致分为下面几层技术层核心职责Java 侧常用技术典型问题模型接入层与大模型 API 通信HttpClient、Spring AI、LangChain4j协议对接、鉴权、超时交互层流式输出、中止、重连WebFlux、SSE、SseEmitter流式响应体验、断线恢复业务编排层Prompt 组装、工具调用、Agent 流程Spring AI Advisors、LangChain4j上下文管理、工具编排数据层私有知识接入、向量检索Elasticsearch、Milvus、Qdrant向量化、混合检索基础设施层网关、限流、监控、日志Nginx、Gateway、Prometheus稳定性与可观测性这套分层结构里Java 工程师最容易忽略的是“交互层”。很多后端做了十几年的接口业务返回值是 JSON 没问题但 AI 应用天然是流式的——用户不想等十几秒看到一整段文字冒出来而是希望像打字机一样逐字看到回答。这就引出 SSE 技术它是 Java 工程师转向 AI 应用开发必须掌握的第一道关。3. Java 接入大模型从原生 HTTP 到框架封装3.1 方案一原生 HttpClient 直接调用不引入任何 AI 框架用 Java 自带 HTTP 客户端直接调大模型接口。这种方式适合团队已经有一套自己的框架封装体系或者只想做一次最简单的联调验证。下面是一个最小示例核心思路是:发 POST 请求开启 streamtrue然后用流式方式逐行读取 SSE 数据// 文件路径src/main/java/com/example/ai/LlmSseClient.java import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; public class LlmSseClient { private static final String API_URL https://your-llm-endpoint/v1/chat/completions; private static final String API_KEY System.getenv(LLM_API_KEY); public static void main(String[] args) throws Exception { String requestBody { model: your-model-name, messages: [ {role: user, content: 用Java写一个冒泡排序} ], stream: true } ; HttpClient client HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(API_URL)) .header(Content-Type, application/json) .header(Authorization, Bearer API_KEY) .POST(HttpRequest.BodyPublishers.ofString(requestBody)) .build(); client.sendAsync(request, HttpResponse.BodyHandlers.ofLines()) .thenAccept(response - { response.body() .filter(line - line.startsWith(data: )) .filter(line - !line.contains([DONE])) .forEach(line - System.out.println(line.substring(6))); }) .join(); } }这个示例最重要的两个点是HttpResponse.BodyHandlers.ofLines()能按行读取响应流适合解析 SSE 格式大模型 SSE 标准格式是data: {JSON}开头最后一条是data: [DONE]。代码中先过滤掉非数据行再截掉data:前缀就能拿到每个增量片段。跑通这个示例后你就明白流式数据的本质了。但从工程化角度说直接用 HttpClient 会面临很多重复劳动不同厂商的 API 协议有细微差别流式格式不规范时还要自己修正工具调用和函数调用的参数解析也得手写。所以更推荐的做法是引入框架。3.2 方案二Spring AISpring 官方在 2024 年正式发布了 Spring AI 项目目的是把大模型接入能力统一抽象成 Spring 风格。它支持主流大模型 OpenAI、通义千问、Ollama 等并且天然支持函数调用、RAG、Advisor 等高级能力。对于已经在用 Spring Boot 的团队接入成本最低。先添加依赖以 Maven 为例!-- 文件路径pom.xml -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-openai/artifactId /dependencySpring AI 的版本迭代比较快不同版本之间的 API 形态差异明显。使用的具体版本建议以官方文档为准下面基于 1.x 的主线写法演示。配置模型地址和密钥放在配置中心或环境变量里# 文件路径src/main/resources/application.yml spring: ai: openai: api-key: ${OPENAI_API_KEY} base-url: ${LLM_BASE_URL:https://api.example.com} chat: options: model: ${LLM_MODEL:your-model-name}然后写一个最简控制器把模型调用能力暴露给前端。注意这里的produces MediaType.TEXT_EVENT_STREAM_VALUE是 SSE 输出的关键// 文件路径src/main/java/com/example/ai/controller/ChatController.java RestController RequestMapping(/api/chat) public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient chatClient; } PostMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestBody ChatRequest request) { return chatClient.prompt() .user(request.getPrompt()) .stream() .content(); } }它背后帮你做了三件事自动拼接对话上下文、解析模型返回、把模型增量响应封装成响应式流。你拿到的是一个FluxStringSpring WebFlux 会把它变成标准 SSE 输出。3.3 方案三LangChain4jLangChain4j 是 LangChain 的 Java 移植版核心卖点是面向 Agent 场景的“链式编排”你可以定义多个工具让大模型自动决定调用哪些工具再根据工具返回结果继续推理。它有清晰的Tool注解、AiServices抽象、ChatMemory等组件适合做客服机器人、流程助手这类复杂应用。与 Spring AI 对比LangChain4j 的编排模型更接近 LangChain社区里大量 Python 侧的语义可以迁移过来。Spring AI 则胜在与 Spring Boot 生态深度融合如果你只需要基础的对话、流式输出选 Spring AI 更轻。3.4 技术栈选型决策建议选型条件推荐方案原因只想快速联调、不引入额外依赖原生 HttpClient零依赖、代码直观已有 Spring Boot 项目需要对话、流式、函数调用Spring AI上手成本最低、统一抽象偏 Agent 编排、复杂工具链、多轮记忆LangChain4j链式编排更灵活、社区概念成熟大并发生产环境框架 自研网关限流、鉴权、监控需要自己做4. SSE 流式输出让大模型回答“逐字出现”4.1 SSE 是什么SSE 全称是 Server-Sent Events服务端推送事件。它和 WebSocket 最大的区别是单向的浏览器通过普通 HTTP 请求建立连接服务端持续把数据推送给浏览器数据格式是data: 内容用换行分隔。大模型对话场景天然适合 SSE因为只需要服务端向客户端单向推送文本增量不需要客户端频繁上行数据。对比一下普通 JSON 接口的过程用户发出请求服务端阻塞等待大模型返回完整答案再一次性返回 JSON前端渲染一整段文字中间用户面对的是一个转圈图标可能等十几秒。换成 SSE 后服务端一边接收大模型增量一边把增量推给前端前端逐字渲染用户体感从“远程请求”变成“对话”。4.2 Java 中实现 SSE 的两种方式如果使用 Spring AI WebFlux上一节的代码已经能工作。如果不想依赖 Spring AI也可以直接用 Spring MVC 里的SseEmitter它不要求引入 WebFlux// 文件路径src/main/java/com/example/ai/controller/SseController.java RestController RequestMapping(/api/sse) public class SseController { GetMapping(value /chat, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter chat(RequestParam String prompt) { SseEmitter emitter new SseEmitter(60_000L); // 这里用线程池模拟异步推送 Executors.newSingleThreadExecutor().submit(() - { try { ListString chunks List.of(你好, , 我是, Java, AI, 助手, 。); for (String chunk : chunks) { emitter.send(chunk); Thread.sleep(300); } emitter.complete(); } catch (Exception e) { emitter.completeWithError(e); } }); return emitter; } }生产中不会真的用Thread.sleep而是把模型返回的增量流接到emitter.send()上。这个例子的价值在于把 SSE 机制本身拆解清楚了一个接口返回的 Body 不是一次性 JSON而是持续写入的文本流。4.3 连接断开与超时SSE 连接有两个经典坑代理缓冲和空闲超时。如果在 Nginx 后面默认会缓存上游响应导致前端等很久才一次性收到数据。需要在代理层关闭缓冲proxy_buffering off; proxy_cache off;同时路由到/api/sse/的 location 建议加大 read timeout否则一个长对话请求可能被 Nginx 主动断开。SSE 本身也有重连机制浏览器内置EventSource断线后会自动重连但如果你用 fetch 接口自建流式解析则需要自己处理重连逻辑。从工程视角建议明确区分“模型正在回答”和“网络断开”两种状态避免前端误判。5. 前端配合 AbortController 实现流式中断很多开发者会忽略一个问题大模型流式输出可能持续几十秒用户中途不耐烦点了“停止生成”这时如果前端只是本地停掉渲染服务端不会收到取消信号模型还在继续生成Token 还在持续计费。正确的做法必须是前端主动断开请求服务端检测到断连后立即停止生成。前端用 fetch 加AbortController就能实现// 文件路径src/main/resources/static/js/chat.js const controller new AbortController(); const { signal } controller; const response await fetch(/api/chat/stream, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ prompt: 用Java写一个冒泡排序 }), signal, }); const reader response.body.getReader(); const decoder new TextDecoder(); function parseSseChunk(chunk) { // 按行拆解SSE数据提取 data: 后的内容 const lines chunk.split(\n); for (const line of lines) { if (line.startsWith(data: )) { const payload line.slice(6); if (payload ! [DONE]) { const data JSON.parse(payload); const delta data.choices?.[0]?.delta?.content; if (delta) { appendMessage(delta); } } } } } while (true) { const { done, value } await reader.read(); if (done) break; parseSseChunk(decoder.decode(value, { stream: true })); } // 停止按钮点击时调用 controller.abort() stopBtn.onclick () controller.abort();注意这里用了reader.read()而不是EventSource。虽然EventSource是新 API但它不支持自定义请求头也不方便在同一个页面对同一个 SSE 路径做动态参数处理更不支持中断信号。所以实际 AI 应用前端的流式消费多数会直接用 fetch ReadableStream。只有当前端真正调用controller.abort()时浏览器才会断开 TCP 连接。后端如果用了 Spring WebFlux断开后Flux的订阅会自动取消模型生成自然停止这笔 Token 就省下来了。6. Agent 开发Java 侧的 Agent 到底是什么Agent 是最近几年 AI 应用最热的词但对多数后端工程师来说它不是一个玄学概念而是一种编程范式。Agent 程序的核心能力是让大模型根据用户请求自主决定“先做什么、再做什么、调用什么工具”并根据工具返回结果继续推理直到完成任务。用 Java 实现一个最简单的 Agent通常需要四个部分大模型本身负责理解意图和生成回复工具层暴露给模型的具体方法比如查库存、算价格、发短信编排层负责轮询、追踪模型需要调用哪个工具并回填结果记忆层保存对话历史和中间推理过程以 LangChain4j 为例定义一个工具类// 文件路径src/main/java/com/example/agent/ToolService.java import dev.langchain4j.agent.tool.Tool; public class ToolService { Tool(计算两个数字的和) public int add(int a, int b) { return a b; } Tool(计算两个数字的乘积) public int multiply(int a, int b) { return a * b; } }然后通过AiServices把它绑定到 Agent 上// 文件路径src/main/java/com/example/agent/AgentDemo.java import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.service.AiServices; // 定义一个接口LangChain4j 会动态生成实现 interface Assistant { String chat(String message); } public class AgentDemo { public static void main(String[] args) { OpenAiChatModel model OpenAiChatModel.builder() .apiKey(System.getenv(OPENAI_API_KEY)) .build(); Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemory(MessageWindowChatMemory.withMaxMessages(20)) .tools(new ToolService()) .build(); String answer assistant.chat(请计算 13 加 27 等于多少); System.out.println(answer); } }运行后模型发现自己不擅长计算但看到了可用的add工具就会构造一次工具调用LangChain4j 自动执行工具方法再把结果交给模型组织最终答案。整个链路对开发者是透明的你只要注册工具和定义接口即可。这个例子里已经包含了一个 mini Agent 的最简闭环。真正复杂的 Agent 还会加入状态机、任务拆解、子 Agent 调度但底层的“模型–工具–回填–再推理”循环是完全一致的。Java 侧的 Agent 能力并不比 Python 弱只是资料相对少需要动手试错。7. Java AI 应用开发学习路线下面这条路线是基于“已经会 Java 基础、想转 AI 应用层”的读者设计的。每个阶段都有一个可验证的产出物不追求全部学完再动手而是每学完一层就能做出一个能运行的小作品。7.1 阶段一Java 基础与面向对象1 周这部分看起来是废话但很多转 AI 的开发者恰恰倒在这里。重点掌握类与对象、接口、集合、泛型、Lambda、Stream 流。实践任务是写一个带泛型的 JSON 解析工具类能解析大模型返回的嵌套结构。产出物不重要重要的是能顺畅读懂 Spring AI 源码里的泛型封装。7.2 阶段二并发编程与数据一致性2 周AI 应用是天然高并发的。重点掌握线程池参数、CompletableFuture 异步编排、ConcurrentHashMap与锁的区别、如何保证写入不重复。实践任务是模拟 100 个用户同时发消息验证大模型调用只触发 100 次请求不产生重复扣费。这里会触及热点问题“java 怎么保证数据一致性”在 AI 场景下靠的不只是数据库事务还有幂等键、请求唯一 ID 和状态机。7.3 阶段三网络编程与 HTTP/SSE1 周掌握 HTTP 协议、RESTful 设计、认证鉴权、SSE 与 WebSocket 的差异。实践任务是把第四节里的原生 HttpClient 示例跑通再改用 Spring WebFlux 重写一遍对比两种写法的差异。能在浏览器 Network 面板里清晰看到event-stream类型的响应就说明这关过了。7.4 阶段四大模型与 AI 应用基础2 周这里才需要理解“提示词”“上下文窗口”“温度”“函数调用”这些概念。建议从可直接调用的 API 入手不急着看模型原理。实践任务是写一个带系统提示词的智能客服 Demo要求它只能根据内置的 FAQ 知识回答答不上来时明确说不知道。这个练习能让你理解 Prompt 工程在应用层的价值。7.5 阶段五Agent、RAG 与向量数据2 周重点学习 LangChain4j 和 Spring AI 的高级功能工具调用、对话记忆、RAG 链路。RAG 需要引入向量库Java 项目最常用的是 Elasticsearch、Milvus、Qdrant。实践任务是做一个“公司知识库问答”把一份 PDF 文档向量化用户提问后先检索相关片段再把片段拼进提示词交给模型。这是当前企业 AI 应用最常见的需求。7.6 阶段六工程化与项目实战持续把前面作品接进真实的 Spring Boot 项目加入统一响应体、全局异常处理、限流降级、日志监控、成本计量。实践任务是做一个带流式对话、可停止生成、有 Token 计数看板的客服页面。能把它部署到测试环境跑通你就已经是一名合格的应用层 AI 工程师。8. 常见问题与排查思路问题现象可能原因排查方式解决方案SSE 连接频繁断开代理层缓冲或空闲超时查看 Nginx 配置浏览器 Network 观察响应状态关闭proxy_buffering加大 read timeout前端收不到增量等全部结束后一次返回服务端没有显式声明text/event-stream看响应头 Content-Type在RequestMapping中声明produces MediaType.TEXT_EVENT_STREAM_VALUE中文回答乱码字符集不一致检查响应头和数据库存储统一 UTF-8网关层不加额外编码转换用 RestTemplate 调用流式接口卡死RestTemplate 默认读完整响应体不适合流式换成 HttpClient 或 WebClient流式场景只使用流式客户端用户点“停止”后仍持续扣费前端没有真正断开或后端未监听连接断开检查网络请求是否 cancelled前端用 AbortController后端避免用线程sleep阻塞多轮对话后模型“失忆”上下文没有拼接历史消息查看发送给模型的请求体引入 ChatMemory 或手动维护消息列表函数调用参数解析失败模型返回格式变化或类型不匹配打印模型的完整原始响应用强类型JsonSchema约束并在工具层做参数校验接口重试导致重复扣费超时后没有幂等机制看日志中同一请求 ID 出现次数请求头携带幂等键网关做去重9. 工程最佳实践与生产落地建议流式接口统一封装。不要让业务代码直接拿到FluxString就裸奔。建议在接口层统一封装成ChatResponse里面包含消息 ID、会话 ID、Token 使用量、结束原因。这样前端切换模型、统计成本时不用改接口。密钥与配置管理。永远不要把 API Key 硬编码在配置文件中。生产环境必须放到配置中心或环境变量里走统一配置管理平台同时定期轮换。日志里禁止输出 Key 和完整 Prompt。敏感信息与安全边界。大模型是无状态的黑盒它回答的内容可能越界也可能被诱导输出内部提示词。应用层必须加两道闸入口做内容安全过滤出口做敏感信息脱敏。对涉及支付、下单、删除等高风险操作AI 只负责生成“操作指令”真正执行前必须经过业务校验和人工确认。超时与重试策略。大模型接口是外部依赖超时和重试不能按照业务接口的默认方式处理。连接超时建议 5 秒读取超时建议 30 秒以上。重试要配合幂等键同一请求只执行一次避免双倍扣费。日志与监控。每个 AI 请求都要记录request_id、模型名、输入 Token 数、输出 Token 数、耗时、结束码。这个日志本身就是一个成本报表。建议在网关层统计 QPS、失败率、平均首字节时间这三个指标足够覆盖多数故障判断。缓存策略。不是所有问题都需要实时调用大模型。对“如何使用导出功能”“退款流程是什么”这类高频且答案稳定的问题可以在问答系统中加一层向量召回缓存模型生成前先查缓存命中直接复用能明显降低延迟和成本。版本兼容意识。无论是 Spring AI 还是 LangChain4jAPI 都在快速迭代。升级框架前建议先读官方 CHANGELOG把新版本的 API 变化列成清单再动手。生产项目尽量锁定版本避免顺手mvn versions:set带来意外的编译错误。10. 总结与下一步Java 在 AI 应用层的价值不是“能不能做”而是“怎么做得更稳、更像一个企业级系统”。对流式输出、Agent 编排、RAG、工程治理这些环节来说Java 生态不仅不缺工具反而在高并发和可维护性上有明显优势。建议下一步从最小闭环开始先跑通一个 Spring AI SSE 的对话接口用 Postman 或浏览器验证流式输出再把前端AbortController接上最后给它加一个带记忆的 Agent 工具。把这个流程完整跑一遍你对 Java AI 应用开发的理解会比看一百篇文章都扎实。学习路线上不要贪多先聚焦“Java 基础 并发 SSE 一个框架”然后是“Agent RAG 工程化”一步一个项目地沉淀。Java 工程师在 AI 时代的位置不是赛道边缘而是把模型能力真正变成交付物的人。