news 2026/8/29 9:50:35

基于Spring AI 2.0的Java代码生成助手:Agent实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Spring AI 2.0的Java代码生成助手:Agent实战解析

Spring AI 2.0 的 Agent 能力,让我愿意重新把 Java 后端和大模型放到一起考虑。平时我们用 ChatGPT 写代码,只能把代码复制来复制去;这次要聊的是在 Spring Boot 工程里自己写一个类似 Claude Code 的代码生成助手,让模型自己读文件、改文件、生成代码,Java 后端也能直接跑通 Agent 流程。先给结论:这个方案真正值得关注的地方,不是“AI 生成了一堆代码”,而是模型通过工具调用真正操作了项目文件,整个过程可控、可审计、可以被 Java 代码接管。适合正在做 AI 应用、想把大模型接入业务系统的 Java 工程师,也适合面试前补 Agent 知识的同学。

下面按我实际的测试顺序,从概念、环境、代码、参数排查一直拆到生产化。

1. Spring AI 2.0 的 Agent 到底是什么,先别急着写代码

1.1 Spring AI 从“调模型”到“让模型干活”

Spring AI 是 Java 生态里的大模型应用框架,它把模型接入、Prompt 模板、结构化输出、向量存储、工具调用这些能力统一到了 Spring Boot 的编程模型里。早期大家用 Spring AI,最常用的是 ChatClient,也就是把用户的对话请求发给模型,然后把文本结果拿回来展示。这个阶段的模型只是“会聊天”,它不能碰真实业务数据,也不能操作文件系统,更不会主动决定下一步要做什么。

到了 2.0,Agent 相关能力被放到了更核心的位置。这里说的 Agent 不是玄学,也没有那么神秘。它的核心循环就是:模型在生成回复的时候,不仅输出文字,还可以输出“我要调用哪个工具、传什么参数”的指令。你的 Java 代码收到这些指令后,去执行真正的文件读写、接口请求或数据库操作,再把执行结果放回给模型,让模型继续推理。这个“模型决策 -> 工具执行 -> 结果回传 -> 再次推理”的循环,是 Agent 的最小单元。

所以 Spring AI 2.0 对 Java 后端最大的意义,是 Agent 开发从“框架帮你封装”变成了“你也能理解、能控制、能扩展”的工程能力。代码生成助手只是其中一个典型场景。

1.2 为什么需要 Agent 来写代码

普通 Chat 模式处理代码任务,会立刻遇到一个现实问题:项目文件太多,上下文塞不下。你不可能把一个完整的 Spring Boot 项目粘贴给模型,token 限制不允许,噪声也太大。

Agent 模式解决问题的思路完全不同。模型不需要一次看到全部文件,它可以先看项目结构,再按需读取指定文件,生成新文件后写入磁盘。整个过程由模型决定下一步做什么,Java 代码只负责安全执行。这个体验和 Claude Code 很像:你在终端里说“帮我加一个登录接口”,它会自己去查看 Controller、Service、Mapper,然后修改相关文件。

但这种能力不是白来的。要落地成 Java 版代码生成助手,你需要自己做三件事:定义模型能调用的工具、维护多轮对话状态、把模型返回的工具调用指令安全地执行出来。这三件事都不难,但每一件都有坑。

1.3 Agent 的边界:不是所有任务都需要 Agent

这里要给新手提个醒。不要一上来就把所有功能都做成 Agent。如果任务只是“翻译一句话”,或者“总结一段文本”,用 ChatClient 就够了,引入工具调用反而增加延迟和失败率。

Agent 适合的任务有三个特征:多步骤、依赖当前环境、需要真实执行操作。代码生成助手完全符合,因为它必须感知项目结构,必须调用文件工具。如果你只是做一个客服问答机器人,不查数据库、不调用文件、不改配置,那还不需要上 Agent。

我的建议是:先跑通一个最小闭环,再谈扩展。下面这部分,我会按“环境准备 -> 工具定义 -> 对话循环 -> 参数调优”的顺序来拆。

2. 开发环境与工程初始化:先把最小工程跑起来

2.1 环境准备:JDK、构建工具和模型 API

代码生成助手是标准 Java 后端项目。我建议环境如下:

  • JDK 17 或更高,Spring Boot 3.x 官方支持 JDK 17;
  • Maven 3.8+ 或 Gradle 8.x,按习惯选;
  • 一个可以调用的大模型 API,优先选 OpenAI 兼容接口;
  • 本机内存至少 8G,16G 会更舒服。

为什么内存不能太低?因为 Spring Boot 本身要占一部分,Agent 请求高并发时,模型响应和文件内容都会在内存里做缓冲。如果只有 4G 内存,跑单条任务可能没事,但连续跑几条就会碰到java: outofmemoryerror: insufficient memory这类问题。这个报错后面会单独说。

模型 API 这块,可以用 OpenAI 官方地址,也可以用国内服务商提供的 OpenAI 兼容接口,比如 DeepSeek、通义等。只要 base-url 和 api-key 能对上,Spring AI 的 OpenAI Starter 通常都能兼容。如果不想依赖公网模型服务,本地 Ollama 也可以作为备选,但代码生成类任务对模型能力要求不低,建议至少用一个中大规模模型。

2.2 创建 Spring Boot 工程

我一般不会从零手写配置,直接用 Spring Initializr 生成工程。语言选 Java,构建工具选 Maven,Spring Boot 版本选 3.x,依赖先加 Web 和 Lombok。Spring AI 相关依赖会在下一步手动加。

初始工程结构不需要复杂,一个入口类、一个配置类、一个 Agent 服务类就够了。记住一个原则:先不要让程序启动即加载一堆 Agent 逻辑,目标是把工程跑起来,然后能调用模型,最后才加工具。

生成后,在application.yml里预留配置项。模型 key 不要写死在文件里,用环境变量注入,这样即使代码不小心提交到仓库,也不会泄露密钥。

2.3 引入 Spring AI 2.0 依赖

Spring AI 目前还处于快速迭代阶段,不同小版本的 API 可能有变化。所以不要记死某个坐标,而是用一个 BOM 统一管理版本。

Maven 里可以这样配:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

然后在 dependencies 里加:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency>

Spring Initializr 如果在生成时选了 Spring AI,它会自动帮你配好仓库。手动加依赖时,记得检查是否包含 Spring 的 Release 或 Milestone 仓库,例如:

<repositories> <repository> <id>spring-milestones</id> <url>https://repo.spring.io/milestone</url> </repository> </repositories>

这里最容易踩的坑是:依赖加完启动报找不到类,大多数时候是 BOM 版本和 Spring Boot 版本不匹配。解决方法是先跑一个样例接口,确认 ChatClient 能注入,再写 Agent 逻辑。

2.4 配置模型客户端

配置文件比想象中简单。最简配置如下:

spring: ai: openai: base-url: ${AI_BASE_URL:https://api.openai.com} api-key: ${AI_API_KEY} chat: options: model: ${AI_MODEL:gpt-4o-mini} temperature: 0.3

如果你用的是 DeepSeek,可以把base-url换成https://api.deepseek.com,模型名换成deepseek-chat。如果你的服务商要求额外请求头,再按官方文档补充。Spring AI 的 OpenAI Starter 整体是兼容这套协议的。

这里有一个经验:第一次启动时,不要急着写复杂提示词。先注入ChatClient,调用一次prompt().user("你好").call().content(),能返回内容就说明配置没问题。这样你能把模型问题、依赖问题、项目代码问题区分开。

3. 从零实现一个类似 Claude Code 的代码生成助手

3.1 先拆解 Claude Code 的核心工作流

要手写一个类似 Claude Code 的助手,先得定义清楚它要做什么。

我理解的核心流程是:

  1. 用户输入一个需求,比如“给项目加一个获取用户列表的接口”;
  2. Agent 读取项目目录结构,了解项目语言和结构;
  3. Agent 读取相关文件,比如 Controller、Service、Mapper;
  4. Agent 设计改动方案,调用写文件工具生成或修改代码;
  5. 助手向用户返回执行结果。

这个流程里的每一步,模型都说了算。Java 代码只负责提供工具和循环。Spring AI 的工具调用能力,正好能用上。

在设计上,我会先定义三个最小工具:listProjectStructurereadFilewriteFile。后续有需要再加runCommand,但不建议默认开启,因为让模型执行任意命令的风险太高。

3.2 用 @Tool 定义文件操作能力

Spring AI 里最常见的工具定义方式是用@Tool注解标记一个 Bean 方法。为了演示,我定义一个ProjectTools组件,并把项目根目录限定在一个固定目录下。

示例代码如下:

@Component public class ProjectTools { private final Path projectRoot; public ProjectTools(@Value("${agent.project-root:./demo-project}") String root) { this.projectRoot = Path.of(root).toAbsolutePath().normalize(); } @Tool("读取项目目录结构,返回目录树") public String listProjectStructure() throws IOException { try (Stream<Path> paths = Files.walk(projectRoot)) { return paths .map(p -> projectRoot.relativize(p).toString()) .limit(500) .collect(Collectors.joining("\n")); } } @Tool("读取指定文件内容,path 是相对项目根目录的路径") public String readFile(String path) throws IOException { Path p = resolveSafe(path); return Files.readString(p); } @Tool("将 content 写入指定文件,path 是相对项目根目录的路径,会覆盖已存在文件") public String writeFile(String path, String content) throws IOException { Path p = resolveSafe(path); Files.createDirectories(p.getParent()); Files.writeString(p, content, StandardCharsets.UTF_8); return "success"; } private Path resolveSafe(String path) throws IOException { Path p = projectRoot.resolve(path).normalize(); if (!p.startsWith(projectRoot)) { throw new IOException("非法路径:" + path); } return p; } }

这段代码有一个关键细节:resolveSafe方法强制把路径规范化后再判断是否在项目根目录内。没有这层保护,模型可能会读出系统文件,也可能往任意目录写文件。生产环境还建议先判断 path 是不是绝对路径,再走后续校验。

3.3 维护对话循环,把工具结果喂回模型

有工具还不够,关键是把模型的工具调用和 Java 工具执行串起来。Spring AI 的具体 API 在不同版本里会有变化,但核心循环是稳定的:

用户消息 -> 模型 -> 如果有工具调用 -> 执行工具 -> 返回结果给模型 -> 模型继续 -> 直到没有工具调用,输出最终文本

如果框架没有自动处理,你需要手动维护一个消息列表。伪代码如下:

List<Message> messages = new ArrayList<>(); messages.add(new UserMessage(userPrompt)); for (int i = 0; i < maxIterations; i++) { ChatResponse response = chatModel.call(new Prompt(messages)); AssistantMessage assistantMessage = response.getResult().getOutput(); messages.add(assistantMessage); if (assistantMessage.hasToolCalls()) { for (ToolCall toolCall : assistantMessage.getToolCalls()) { Object result = toolExecutor.execute(toolCall.name(), toolCall.arguments()); messages.add(new ToolResponseMessage(toolCall.id(), result)); } } else { return assistantMessage.getText(); } } throw new RuntimeException("Agent 超出最大迭代次数");

这段代码不是某个版本的精确 API,但理解了它,你就能看懂官方封装的自动执行逻辑。maxIterations建议设为 5 到 10,避免模型陷入死循环。如果模型反复调用同一个工具,日志里会非常明显。

3.4 把模型输出转换为文件变更

模型最终输出有两种情况:一是通过工具完成了文件写入,二是只输出建议代码,没有实际落盘。对于代码生成助手,我们更希望它真正落盘。

为了让写文件更安全,我建议在writeFile前增加一个确认回调;如果是团队内部使用,可以先让模型直接写,再在业务层记录日志。每个写操作至少包含:文件路径、操作人、会话 ID、时间、变更前后摘要。这样即使模型生成错误代码,也能追踪到是哪一轮对话触发的。

另一个经验是:写文件时一定要用 UTF-8,并且不要用默认本地编码。Windows 机器默认可能是 GBK,编码不对会导致生成的 Java 类注释或中文字符串变成乱码。

4. 让助手真正可用:上下文、项目感知和路径安全

4.1 会话历史与上下文窗口

Agent 与平时一问一答有一个显著差异:它需要记住自己做过什么。否则一个多步骤任务,模型读完了文件,下一步就忘了。

最简单的做法是用一个List<Message>保存当前会话消息,每次请求都带上。在 Spring Boot 项目里,可以用ConcurrentHashMap<String, List<Message>>按会话 ID 存储。注意线程安全,不要再为每个请求创建一个只读的局部变量。

但会话历史不能无限增长。模型上下文窗口有限,文件内容、目录结构都会占用 token。我在实测时发现,连续读取三四个大文件后,模型就开始截断或忽略之前的指令。解决办法有几个:

  • 超大文件只读取关键片段,比如方法签名和实体定义;
  • 让模型先读目录,再决定读哪个文件;
  • 定期对旧消息做摘要,把摘要放到上下文里。

没有统一标准,但原则是:上下文里放“够用的信息”,而不是“全部信息”。

4.2 项目结构感知:先给一张地图

Claude Code 给人感觉很聪明,一个很重要的原因是它知道整个项目长什么样。对应到我们的实现,就是给模型一个listProjectStructure工具。

第一次进入会话时,可以自动调用这个工具,把目录树返回给模型。目录树不要无限展开,忽略targetnode_modules.git这类目录,否则模型会被噪声干扰。

示例实现里,我用Files.walklimit(500),这是一种粗暴但有效的限制。生产环境可以改成读取.gitignore,把忽略规则也应用到 Agent 的目录遍历上。这样的话,模型看到的项目结构和程序员日常看到的保持一致。

4.3 路径安全与写文件防护

这是整个项目里最重要的边界。模型不是人,它可能因为理解错误,生成一个../../etc的路径,也可能写文件时把整个文件清空。

我推荐的防护方案有三层。

第一层,路径归一化校验。所有相对路径都要经过resolve().normalize(),然后判断是否以项目根目录开头。不是就拒绝。

第二层,写文件前进行内容检查。如果模型返回的content为空,或者文件原本很大但新内容很小,要触发警告。可以在模型写入前做一个 diff,超过某个变化比例就暂停。

第三层,文件备份。正式环境可以在写入前把原文件复制到.backup目录。这个成本不高,但能救回很多“AI 误操作”。

4.4 失败边界与降级策略

Agent 不是百分百可靠。常见失败有几种:模型返回格式错误、工具调用参数缺少字段、文件写入失败、模型超时。每类失败都要有对应的处理方式。

工具调用参数错误时,不要直接把异常抛给用户。更好的做法是把异常信息封装成一条工具结果,回传给模型,让模型修正参数后重试。比如readFile抛非法路径,就返回“路径必须在项目根目录内”。

模型超时时,如果是一次性任务,可以设置业务级别超时并返回“请稍后再试”。如果是代码生成任务,保留已经执行的文件操作,不要回滚全部,保证原子性的代价太高。这里的原则是:Agent 应该有能力告诉用户“我执行到了哪一步、哪些成功了、哪些失败了”,而不是只扔一个堆栈异常。

5. 参数调优和问题排查:从能跑到好用

5.1 参数取舍:temperature、maxTokens、timeout

代码生成任务与闲聊任务不同,对确定性要求更高。temperature太高,模型会生成风格飘忽不定的代码,有时甚至会凭空发明不存在的 API。我建议:

  • temperature:0.2 到 0.4 之间;
  • maxTokens:按生成代码的规模调整,2000 到 8000 都算正常;
  • timeout:默认值可能不够,Agent 要连续调用多个工具,单次请求 30 秒以上很常见,把超时配置放大到 60 秒或 120 秒;
  • topP:保持默认或在 0.9 左右,不要和 temperature 同时拉满。

这些参数没有绝对标准。实际调参时,用同一个 Prompt 和同一个小任务,修改一个参数跑三轮,观察输出变化,才能找到适合你模型的组合。

5.2 验证助手是否“真会干活”

我建议按下面三个级别验证。

第一级,模型能正常聊天。在同一个工程里,先调用 ChatClient,确认模型连接没问题。

第二级,模型能调用单个工具。比如让模型调用listProjectStructure,然后看返回的目录结构是否被模型理解。

第三级,完整任务。给它一个真实需求,比如“读取UserController.java,新增一个GET /users/{id}接口”,然后检查文件是否真的发生变更。

不要一上来就让它改整个项目。先用一个小模块跑通,再逐步加大范围。这样出了问题,你知道该排查模型、工具,还是工作流。

5.3 常见报错与排查顺序

我在实际测试中遇到过几类高频问题,这里给你一个排查顺序。

  1. 模型不调用工具。先看系统提示词里是否说清楚“你有这些工具可以调用”,再看工具描述是否足够具体。描述里必须有“做什么、参数是什么、何时用”。
  2. 工具调用后模型突然不继续了。检查工具结果是不是太长,可能把上下文塞满了。简化返回内容。
  3. 报错the agent execution provider did not respond in time. this may indicate the...。先看模型服务端是否过载,再看超时配置。注意 Agent 执行可能包含多轮工具调用,总耗时比单次请求更长。
  4. 报错java: outofmemoryerror: insufficient memory。先加大 JVM 堆内存,比如-Xmx2g,再降低并发量。读大文件时,避免一次性把整个文件内容装进消息列表。
  5. 模型返回 529。这是模型服务端限流,设置指数退避重试,同时把并发压下来。
  6. 写出的文件乱码。检查文件编码是否 UTF-8,尤其是 Windows 环境。

这些报错不全是框架问题。我的经验是:先看日志里的工具调用记录,再去看模型返回,最后才改代码。

5.4 资源占用监控

Agent 应用和后端接口不一样,它可能一次性占用较长连接和内存。启动时加两个 JVM 参数会有帮助:

java -Xms512m -Xmx2g -jar code-agent.jar

同时观察三件事:并发数、平均任务耗时、内存增长曲线。如果内存持续上升,优先怀疑会话历史没有清理,文件工具返回了大量内容并长期留在消息列表里。比较稳妥的做法是给每个会话设置最大消息数,超过之后自动丢弃最旧的消息。

6. 从 Demo 到生产:接口化、并发和日志

6.1 把助手包装成 REST API

代码生成助手不能只在 IDE 里跑本地方法,给它一个 HTTP 入口,才能被其他系统复用。我一般会暴露一个简单接口:

@RestController @RequestMapping("/api/agent") public class AgentController { private final CodeAgent codeAgent; public AgentController(CodeAgent codeAgent) { this.codeAgent = codeAgent; } @PostMapping("/run") public AgentResult run(@RequestBody AgentRequest request) { return codeAgent.run(request.sessionId(), request.message()); } }

AgentRequest至少包含两个字段:sessionId和 `

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

Ubuntu 26.04安装与配置完整指南:从U盘启动到开发环境搭建

很多开发者第一次接触 Linux&#xff0c;不是从服务器开始的&#xff0c;而是从“想换掉 Windows 开发环境”开始的。装 Ubuntu 这件事&#xff0c;看起来只是下载一个镜像、写进 U 盘、点几下鼠标&#xff0c;但真正动手时&#xff0c;很多人会卡在启动盘不识别、分区不敢动、…

作者头像 李华
网站建设 2026/8/29 9:49:43

数据结构课程设计实战:飞机票、Trie树、交通咨询与搜索引擎系统解析

简介&#xff1a;数据结构是计算机科学的核心基础&#xff0c;贯穿线性表、树形结构、图论算法与检索排序等知识体系。Trie树和后缀树作为高效的字符串匹配结构&#xff0c;支撑着搜索引擎的自动补全与子串查找&#xff1b;图论中的Dijkstra与Floyd算法则解决了交通咨询系统中的…

作者头像 李华
网站建设 2026/8/29 9:48:11

VMD-SSA-LSTM光伏功率预测:变分模态分解与麻雀搜索优化时序模型

简介&#xff1a;时序预测是机器学习在能源领域的重要应用&#xff0c;其核心挑战在于处理强非平稳、多噪声的信号。变分模态分解&#xff08;VMD&#xff09;通过频带分离将复杂序列拆解为多个模态&#xff0c;降低建模难度&#xff1b;麻雀搜索算法&#xff08;SSA&#xff0…

作者头像 李华
网站建设 2026/8/29 9:45:31

基于FMCW与声学信号融合的智能手机非接触手势识别系统

简介&#xff1a;本资源是一套基于FMCW雷达与声学信号双模态融合的智能手机手势识别Matlab实现方案&#xff0c;面向计算机、电子信息工程及数学等专业的本科生&#xff0c;适用于课程设计、期末大作业与毕业设计等实践环节&#xff0c;解决传统触摸交互局限性下的自然人机交互…

作者头像 李华
网站建设 2026/8/29 9:43:21

2026锦州工程建筑材料检测排名 TOP5 CMA 资质提供钢材检测、水泥检测、砂石检测 全覆盖联系方式推荐

锦州建材检测市场近年愈发火热&#xff0c;各类检测机构如雨后春笋般涌现&#xff0c;但其中鱼龙混杂、良莠不齐。建筑总包单位、建材生产厂家、市政工程项目、装修建设企业在选材验收时&#xff0c;稍有不慎便可能遇上无资质机构&#xff0c;其出具的检测报告无法用于工程报审…

作者头像 李华
网站建设 2026/8/29 9:40:45

Godot 场景脚本如何拆分:3 步实现逻辑与表现分离

Godot 场景脚本如何拆分&#xff1a;3 步实现逻辑与表现分离 【免费下载链接】godot Godot Engine – Multi-platform 2D and 3D game engine 项目地址: https://gitcode.com/GitHub_Trending/go/godot 在 Godot Engine&#xff08;跨平台 2D/3D 游戏引擎&#xff09;里…

作者头像 李华