Spring AI 实战进阶:从0开发IDEA插件版AI代码助手(Java全栈+上下文感知))
前言作为 Java 开发者我们每天都在重复编写 CRUD 代码、调试语法错误、优化性能问题 —— 这些机械性工作占用了大量时间而市面上的通用 AI 代码助手如 Copilot往往无法精准感知项目上下文比如项目的包结构、依赖版本、数据库表结构生成的代码需要大量修改才能落地。笔者近期基于 Spring AIIDEA 插件开发了一款定制化 AI 代码助手后端基于 Spring AI 整合 JavaParser、Maven API 实现代码解析与生成前端通过 IDEA 插件提供对话窗口和一键插入代码功能支持需求描述→完整代码生成代码优化、上下文感知、补全三大核心能力。本文将从实战角度完整拆解这款 AI 代码助手的开发全流程所有代码均为生产环境可直接复用的实战代码同时结合可视化图表清晰呈现核心逻辑希望能帮你打造专属的 AI 代码提效工具。一、项目背景与架构设计1.1 项目定位与核心需求项目定位基于 Spring AI 的 Java 代码生成工具以IDEA 插件前端 Spring Boot 后端核心的架构解决通用 AI 代码助手上下文脱节、代码适配性差的问题专注 Java 项目的代码生成、优化与补全。核心需求维度核心需求技术挑战代码生成输入需求描述生成 ControllerServiceMapper 完整代码Spring AI 精准 Prompt 工程、Java 语法合规性校验代码优化自动修复语法错误、优化性能如 SQL 优化、循环优化JavaParser 解析代码 AST、Spring AI 调用大模型分析上下文感知感知当前项目的包结构、依赖、数据库表结构IDEA 插件获取项目上下文、后端存储上下文信息交互体验IDEA 内对话窗口、一键插入生成的代码IDEA 插件 Swing 开发、前后端通信协议设计1.2 整体架构设计以下是 AI 代码助手的核心架构图清晰呈现前后端交互与核心模块逻辑1.3 技术栈选型结合 Java 生态与 IDEA 插件开发规范最终选型如下技术领域选型选型理由后端核心Spring Boot 3.2 Spring AI 0.8.1Spring AI 原生适配 Spring 生态支持多模型统一调用代码解析JavaParser 3.25.10轻量、高效的 Java 代码 AST 解析库支持代码生成 / 校验Maven 交互Maven API 3.9.6解析项目 pom.xml获取依赖与包结构IDEA 插件IntelliJ Platform SDK 2023.2官方 SDK支持 IDEA 插件全功能开发前端交互Swing OkHttp 4.12.0Swing 实现 IDEA 内窗口OkHttp 实现前后端通信大模型GPT-4 通义千问可选GPT-4 代码生成质量高通义千问支持私有化部署存储MySQL 8.0 Redis 7.0存储项目上下文、生成的代码片段二、核心技术架构拆解2.1 后端核心Spring AI 工具调用体系2.1.1 Spring AI 核心配置首先完成 Spring AI 的基础配置支持多模型调用以 OpenAI 为例/** * Spring AI核心配置类 */ Configuration public class SpringAiConfig { /** * 配置OpenAI客户端 */ Bean public OpenAiChatClient openAiChatClient() { // 从配置文件读取API Key和Base URL String apiKey System.getenv(OPENAI_API_KEY); String baseUrl https://api.openai.com/v1; OpenAiApi openAiApi new OpenAiApi(baseUrl, apiKey); // 创建客户端并配置默认参数 OpenAiChatClient client new OpenAiChatClient(openAiApi); client.setTemperature(0.2); // 低温度保证代码稳定性 client.setModel(gpt-4); return client; } }2.1.2 工具调用层JavaParser Maven API工具调用层是区别于通用 AI 的核心 —— 通过 JavaParser 解析代码 AST、Maven API 解析项目依赖让 AI 生成的代码贴合项目实际/** * 项目上下文解析服务基于Maven APIJavaParser解析项目信息 */ Service public class ProjectContextParser { /** * 解析Maven项目的包结构、依赖信息 * param pomPath pom.xml文件路径从IDEA插件传递 */ public ProjectContext parseMavenProject(String pomPath) throws Exception { ProjectContext context new ProjectContext(); // 1. 解析pom.xml获取基础信息 File pomFile new File(pomPath); MavenXpp3Reader reader new MavenXpp3Reader(); Model model reader.read(new FileReader(pomFile)); // 设置groupId、artifactId、版本 context.setGroupId(model.getGroupId()); context.setArtifactId(model.getArtifactId()); context.setBasePackage(model.getGroupId() . model.getArtifactId()); // 2. 解析依赖信息 ListString dependencies new ArrayList(); for (Dependency dep : model.getDependencies()) { dependencies.add(dep.getGroupId() : dep.getArtifactId() : dep.getVersion()); } context.setDependencies(dependencies); // 3. 解析项目源码目录简化版 File srcDir new File(pomFile.getParentFile(), src/main/java); if (srcDir.exists()) { context.setSrcRootPath(srcDir.getAbsolutePath()); // 解析已存在的包结构 ListString packages parsePackages(srcDir); context.setPackages(packages); } return context; } /** * 解析源码目录下的所有包 */ private ListString parsePackages(File srcDir) { ListString packages new ArrayList(); // 递归遍历源码目录解析包名简化版 File[] files srcDir.listFiles(); if (files null) return packages; for (File file : files) { if (file.isDirectory()) { String packageName file.getAbsolutePath().replace(srcDir.getAbsolutePath(), ) .replace(File.separator, .); if (!packageName.isEmpty()) { packages.add(packageName.substring(1)); // 去掉开头的. } packages.addAll(parsePackages(file)); } } return packages; } /** * 解析Java代码AST校验语法正确性 */ public boolean validateJavaCode(String code) { try { // 使用JavaParser解析代码检测语法错误 CompilationUnit cu StaticJavaParser.parse(code); ListProblem problems cu.getProblems(); return problems.isEmpty(); } catch (Exception e) { return false; } } } // 项目上下文实体类 Data public class ProjectContext { private String groupId; // 项目groupId private String artifactId; // 项目artifactId private String basePackage; // 基础包名 private ListString dependencies; // 依赖列表 private ListString packages; // 包结构 private String srcRootPath; // 源码根路径 private String projectId; // IDEA插件传递的项目唯一标识 }2.2 前端核心IDEA 插件开发基础2.2.1 IDEA 插件工程搭建创建插件工程使用 IntelliJ IDEA 新建IntelliJ Platform Plugin工程选择 SDK 版本2023.2配置 plugin.xml定义插件入口、窗口布局idea-plugin idcom.ai.code.assistant/id nameAI Code Assistant/name version1.0/version vendor emailyouremail.com urlhttps://your.siteYour Name/vendor description 基于Spring AI的Java代码助手支持上下文感知的代码生成与优化 /description !-- 注册Action用于打开对话窗口 -- actions action idAiCodeAssistant.OpenDialog classcom.ai.code.assistant.action.OpenAiDialogAction textAI Code Assistant descriptionOpen AI Code Assistant Dialog add-to-group group-idEditorPopupMenu anchorfirst/ keyboard-shortcut keymap$default first-keystrokectrl alt A/ /action /actions extensions defaultExtensionNscom.intellij !-- 注册自定义窗口 -- toolWindow idAI Code Assistant anchorright factoryClasscom.ai.code.assistant.window.AiToolWindowFactory/ /extensions /idea-plugin2.2.2 对话窗口开发Swing开发 IDEA 内的对话窗口支持输入需求、展示生成的代码/** * AI代码助手对话窗口 */ public class AiCodeDialog extends JDialog { private JTextArea inputArea; // 需求输入框 private JTextPane resultArea; // 代码结果展示框 private JButton generateBtn; // 生成按钮 private JButton insertBtn; // 插入代码按钮 private Project currentProject; // 当前IDEA项目 public AiCodeDialog(Project project) { super(WindowManager.getInstance().getFrame(project), AI Code Assistant, Dialog.ModalityType.MODELESS); this.currentProject project; initUI(); // 初始化UI setSize(800, 600); setLocationRelativeTo(null); } /** * 初始化UI组件 */ private void initUI() { // 1. 输入区域 inputArea new JTextArea(5, 50); inputArea.setPlaceholder(请输入代码生成需求例如生成用户管理的ControllerServiceMapper); JScrollPane inputScroll new JScrollPane(inputArea); // 2. 结果展示区域支持语法高亮 resultArea new JTextPane(); resultArea.setContentType(text/java); JScrollPane resultScroll new JScrollPane(resultArea); // 3. 按钮区域 generateBtn new JButton(生成代码); insertBtn new JButton(插入到编辑器); insertBtn.setEnabled(false); // 初始禁用 // 4. 布局 JPanel panel new JPanel(new BorderLayout()); JPanel topPanel new JPanel(new BorderLayout()); topPanel.add(new JLabel(需求描述), BorderLayout.NORTH); topPanel.add(inputScroll, BorderLayout.CENTER); JPanel btnPanel new JPanel(); btnPanel.add(generateBtn); btnPanel.add(insertBtn); panel.add(topPanel, BorderLayout.NORTH); panel.add(resultScroll, BorderLayout.CENTER); panel.add(btnPanel, BorderLayout.SOUTH); // 5. 绑定事件 generateBtn.addActionListener(e - generateCode()); insertBtn.addActionListener(e - insertCodeToEditor()); add(panel); } /** * 生成代码调用后端接口 */ private void generateCode() { // 1. 采集项目上下文 ProjectContext context collectProjectContext(); // 2. 构建请求参数 CodeGenerateRequest request new CodeGenerateRequest(); request.setRequirement(inputArea.getText()); request.setProjectContext(context); // 3. 调用后端接口OkHttp实现略 OkHttpClient client new OkHttpClient(); // ... 发送POST请求获取生成的代码 // 4. 展示结果 resultArea.setText(generatedCode); insertBtn.setEnabled(true); } /** * 采集当前IDEA项目的上下文 */ private ProjectContext collectProjectContext() { ProjectContext context new ProjectContext(); // 1. 获取项目根路径 String projectPath currentProject.getBasePath(); // 2. 获取pom.xml路径Maven项目 VirtualFile pomFile currentProject.getBaseDir().findChild(pom.xml); if (pomFile ! null) { context.setPomPath(pomFile.getPath()); } // 3. 获取当前编辑器的包路径 Editor editor FileEditorManager.getInstance(currentProject).getSelectedTextEditor(); if (editor ! null) { PsiFile psiFile PsiDocumentManager.getInstance(currentProject).getPsiFile(editor.getDocument()); if (psiFile instanceof PsiJavaFile) { PsiJavaFile javaFile (PsiJavaFile) psiFile; context.setCurrentPackage(javaFile.getPackageName()); } } context.setProjectId(currentProject.getName()); return context; } /** * 将生成的代码插入到当前编辑器 */ private void insertCodeToEditor() { Editor editor FileEditorManager.getInstance(currentProject).getSelectedTextEditor(); if (editor null) return; // 获取选中区域无选中则插入到光标位置 Document document editor.getDocument(); SelectionModel selectionModel editor.getSelectionModel(); int start selectionModel.getSelectionStart(); int end selectionModel.getSelectionEnd(); // 写入代码 WriteCommandAction.runWriteCommandAction(currentProject, () - { document.replaceString(start, end, resultArea.getText()); }); // 取消选中光标移到末尾 selectionModel.removeSelection(); editor.getCaretModel().moveToOffset(start resultArea.getText().length()); } }三、核心功能实现3.1 代码生成ControllerServiceMapper 完整生成3.1.1 Prompt 工程核心逻辑Prompt 工程是代码生成质量的关键 —— 结合项目上下文让 AI 生成的代码直接贴合项目包结构、依赖版本/** * Prompt工程服务动态拼接上下文生成高质量Prompt */ Service public class PromptEngineeringService { /** * 生成代码生成的Prompt * param requirement 用户需求 * param context 项目上下文 */ public Prompt buildGeneratePrompt(String requirement, ProjectContext context) { // 1. 构建系统提示词核心规则 String systemPrompt 你是一位资深Java后端开发工程师精通Spring Boot、MyBatis、MySQL。 请根据以下需求和项目上下文生成符合规范的Java代码 1. 包结构必须符合项目基础包%s 2. 代码必须兼容项目依赖版本优先使用项目已引入的依赖 3. 生成完整的ControllerServiceMapper层包含必要的注释、异常处理 4. 代码风格符合阿里巴巴Java开发手册 5. 只返回代码不返回多余解释 项目上下文 - 基础包名%s - 已存在的包%s - 项目依赖%s .formatted(context.getBasePackage(), context.getBasePackage(), String.join(,, context.getPackages()), String.join(,, context.getDependencies())); // 2. 构建用户提示词 String userPrompt 需求 requirement; // 3. 创建Prompt对象 return new Prompt( List.of( new SystemMessage(systemPrompt), new UserMessage(userPrompt) ) ); } }3.1.2 代码生成核心接口/** * 代码生成核心接口 */ RestController RequestMapping(/api/code) public class CodeGenerateController { Autowired private OpenAiChatClient openAiChatClient; Autowired private PromptEngineeringService promptService; Autowired private ProjectContextParser contextParser; Autowired private ProjectContextRepository contextRepository; /** * 生成代码接口 */ PostMapping(/generate) public ResultString generateCode(RequestBody CodeGenerateRequest request) { try { // 1. 解析项目上下文如果未存储 ProjectContext context; if (request.getProjectContext().getProjectId() ! null) { context contextRepository.findByProjectId(request.getProjectContext().getProjectId()); if (context null) { // 解析pom.xml生成上下文 context contextParser.parseMavenProject(request.getProjectContext().getPomPath()); context.setProjectId(request.getProjectContext().getProjectId()); contextRepository.save(context); // 存储上下文 } } else { context request.getProjectContext(); } // 2. 构建Prompt Prompt prompt promptService.buildGeneratePrompt(request.getRequirement(), context); // 3. 调用Spring AI生成代码 AiResponse response openAiChatClient.generate(prompt); String generatedCode response.getGeneration().getText(); // 4. 校验代码语法 boolean isValid contextParser.validateJavaCode(generatedCode); if (!isValid) { // 重新生成简化版实际可加入重试逻辑 generatedCode regenerateCode(prompt); } return Result.success(generatedCode); } catch (Exception e) { log.error(生成代码失败, e); return Result.error(生成代码失败 e.getMessage()); } } /** * 代码校验失败时重新生成 */ private String regenerateCode(Prompt prompt) { // 追加提示词要求修复语法错误 Prompt newPrompt new Prompt( prompt.getMessages().stream() .collect(Collectors.toList()) .add(new UserMessage(以上代码存在语法错误请修复后重新生成只返回修复后的代码)) ); return openAiChatClient.generate(newPrompt).getGeneration().getText(); } }3.1.3 效果示例用户输入需求生成用户管理的 ControllerServiceMapper包含查询、新增、修改、删除接口生成的代码Controller 层package com.example.demo.controller; import com.example.demo.entity.User; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 用户管理控制器 * author AI Code Assistant * date 2024-05-20 */ RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; /** * 查询所有用户 */ GetMapping public ListUser listAll() { return userService.listAll(); } /** * 根据ID查询用户 */ GetMapping(/{id}) public User getById(PathVariable Long id) { return userService.getById(id); } /** * 新增用户 */ PostMapping public boolean save(RequestBody User user) { return userService.save(user); } /** * 修改用户 */ PutMapping(/{id}) public boolean update(PathVariable Long id, RequestBody User user) { user.setId(id); return userService.update(user); } /** * 删除用户 */ DeleteMapping(/{id}) public boolean delete(PathVariable Long id) { return userService.delete(id); } }3.2 代码优化自动修复语法错误与性能问题3.2.1 代码优化 Prompt 设计/** * 构建代码优化的Prompt */ public Prompt buildOptimizePrompt(String code, String optimizeType) { String systemPrompt 你是一位资深Java性能优化工程师精通Java语法、性能调优。 请根据指定类型优化以下代码 优化类型%s 优化规则 1. 修复语法错误保证代码可编译 2. 性能优化需给出具体的优化点如循环优化、SQL优化、集合使用优化 3. 保留原有业务逻辑只优化语法和性能 4. 输出优化后的代码优化说明分开展示 .formatted(optimizeType); return new Prompt( List.of( new SystemMessage(systemPrompt), new UserMessage(需要优化的代码\n code) ) ); }3.2.2 代码优化接口实现/** * 代码优化接口 */ PostMapping(/optimize) public ResultCodeOptimizeResponse optimizeCode(RequestBody CodeOptimizeRequest request) { try { // 1. 构建优化Prompt Prompt prompt promptService.buildOptimizePrompt(request.getCode(), request.getOptimizeType()); // 2. 调用AI优化代码 AiResponse response openAiChatClient.generate(prompt); String result response.getGeneration().getText(); // 3. 解析优化结果代码说明 CodeOptimizeResponse responseVO parseOptimizeResult(result); return Result.success(responseVO); } catch (Exception e) { log.error(优化代码失败, e); return Result.error(优化代码失败 e.getMessage()); } } /** * 解析优化结果简化版实际可按固定格式解析 */ private CodeOptimizeResponse parseOptimizeResult(String result) { CodeOptimizeResponse response new CodeOptimizeResponse(); // 假设结果以优化后代码和优化说明分隔 String[] parts result.split(优化说明); if (parts.length 1) { response.setOptimizedCode(parts[0].replace(优化后代码, ).trim()); } if (parts.length 2) { response.setOptimizeDesc(parts[1].trim()); } return response; }3.2.3 优化效果示例原始代码性能问题// 循环查询数据库性能差 public ListUser listUsers(ListLong ids) { ListUser users new ArrayList(); for (Long id : ids) { User user userMapper.getById(id); // 每次循环查库 users.add(user); } return users; }优化后代码// 批量查询数据库减少IO次数 public ListUser listUsers(ListLong ids) { // 优化点批量查询替代循环查询减少数据库连接次数 if (CollectionUtils.isEmpty(ids)) { return Collections.emptyList(); } return userMapper.listByIds(ids); // 批量查询 }优化说明性能问题循环遍历 ID 列表每次查询数据库导致多次 IO 操作性能低下优化方案使用 MyBatis 的批量查询方法 listByIds一次 SQL 查询获取所有数据额外优化增加空值判断避免空指针异常。3.3 知识注入项目上下文感知的代码补全3.3.1 上下文感知核心逻辑上下文感知的代码补全是核心亮点 ——IDEA 插件实时采集当前编辑文件的包结构、已导入的类后端结合这些信息生成精准的补全建议/** * 代码补全服务上下文感知的补全建议 */ Service public class CodeCompletionService { /** * 生成上下文感知的代码补全建议 */ public ListString completeCode(CodeCompletionRequest request) { // 1. 构建补全Prompt String systemPrompt 请根据当前Java文件的上下文生成代码补全建议 1. 补全建议必须符合当前包结构%s 2. 优先使用已导入的类%s 3. 补全建议简洁每条不超过50个字符 4. 只返回补全建议列表每行一个 .formatted(request.getCurrentPackage(), String.join(,, request.getImportedClasses())); String userPrompt 需要补全的代码片段\n request.getCodeSnippet(); Prompt prompt new Prompt( List.of( new SystemMessage(systemPrompt), new UserMessage(userPrompt) ) ); // 2. 调用AI生成补全建议 AiResponse response openAiChatClient.generate(prompt); String result response.getGeneration().getText(); // 3. 解析补全建议列表 return Arrays.stream(result.split(\n)) .map(String::trim) .filter(s - !s.isEmpty()) .collect(Collectors.toList()); } }3.3.2 IDEA 插件端补全联动在 IDEA 插件中监听编辑器的输入事件实时调用补全接口/** * 代码补全监听器监听编辑器输入实时调用补全接口 */ public class CodeCompletionListener extends TypedActionHandlerBase { Override public void execute(NotNull Editor editor, char c, NotNull DataContext dataContext) { // 1. 采集当前代码片段光标前100个字符 CaretModel caretModel editor.getCaretModel(); int offset caretModel.getOffset(); Document document editor.getDocument(); String codeSnippet document.getText(new TextRange(Math.max(0, offset - 100), offset)); // 2. 采集上下文当前包、已导入的类 Project project CommonDataKeys.PROJECT.getData(dataContext); PsiFile psiFile PsiDocumentManager.getInstance(project).getPsiFile(document); CodeCompletionRequest request new CodeCompletionRequest(); request.setCodeSnippet(codeSnippet); if (psiFile instanceof PsiJavaFile) { PsiJavaFile javaFile (PsiJavaFile) psiFile; request.setCurrentPackage(javaFile.getPackageName()); // 获取已导入的类 ListString importedClasses javaFile.getImportList().getAllImports().stream() .map(ImportStatement::getQualifiedName) .collect(Collectors.toList()); request.setImportedClasses(importedClasses); } // 3. 调用后端补全接口异步避免阻塞编辑器 CompletableFuture.runAsync(() - { ListString completions callCompletionApi(request); // 4. 在编辑器中展示补全建议使用IDEA的CompletionContributor showCompletionSuggestions(editor, completions); }); } }四、实战部署插件打包与私有仓库发布4.1 IDEA 插件打包配置打包参数在build.gradle中配置插件打包信息plugins { id java id org.jetbrains.intellij version 1.17.3 } intellij { version 2023.2 type IC plugins [java] } sourceCompatibility 17 targetCompatibility 17 // 打包配置 tasks.buildPlugin { archiveBaseName ai-code-assistant archiveVersion 1.0.0 destinationDirectory file($projectDir/dist) }执行打包命令./gradlew buildPlugin打包完成后在dist目录下生成ai-code-assistant-1.0.0.zip插件包。4.2 私有仓库发布搭建私有插件仓库使用 Nexus 或 JetBrains Plugin Repository 搭建私有仓库上传插件包将打包后的 zip 文件上传到私有仓库IDEA 配置私有仓库打开 IDEA → Settings → Plugins → Gear 图标 → Manage Plugin Repositories添加私有仓库地址如http://your-nexus-url/repository/idea-plugins/在插件市场中搜索AI Code Assistant即可安装。4.3 后端服务部署打包后端服务mvn clean package -DskipTests部署到服务器将target/ai-code-assistant-1.0.0.jar上传到服务器执行启动命令nohup java -jar ai-code-assistant-1.0.0.jar --spring.profiles.activeprod app.log 21 配置反向代理使用 Nginx 配置域名和 HTTPS对外提供 API 服务。五、实战踩坑与优化方案问题分类具体问题根因最终解决方案IDEA 插件插件启动时获取不到项目上下文插件加载时机过早项目未完全初始化在projectOpened事件中初始化上下文采集逻辑代码生成AI 生成的代码包名错误Prompt 中上下文拼接不完整优化 Prompt强制 AI 使用项目基础包名增加校验逻辑性能问题代码生成响应慢5sAI 调用 上下文解析耗时1. 缓存项目上下文Redis2. 异步生成代码返回任务 ID 轮询结果语法校验JavaParser 校验误判JavaParser 版本与 IDEA SDK 不兼容统一使用 IDEA 内置的 Java 解析器PSI API替代 JavaParser插件交互插入代码时格式错乱换行符 / 缩进不一致插入前格式化代码CodeStyleManager.getInstance(project).reformat(psiElement)六、总结与进阶规划6.1 核心总结架构设计以Spring AI 为核心 工具调用层为差异化结合 IDEA 插件实现前后端联动解决通用 AI 代码助手的上下文脱节问题核心能力通过 Prompt 工程实现高质量代码生成基于 JavaParser 实现语法校验基于 IDEA PSI API 实现上下文采集部署落地完成插件打包、私有仓库发布、后端服务部署形成完整的提效工具链6.2 进阶规划私有化部署支持通义千问、文心一言等国产大模型私有化部署满足企业数据安全需求本地知识库接入项目的数据库表结构、接口文档进一步提升代码生成的精准度批量代码生成支持根据数据库表结构一键生成整个模块的代码团队协作增加代码片段共享、团队定制 Prompt 功能多 IDE 适配支持 Eclipse、VS Code 等其他 IDE扩大使用范围。最后本文从实战角度完整拆解了基于 Spring AI 的 IDEA 插件版 AI 代码助手开发覆盖了后端 Spring AI 整合、IDEA 插件开发、Prompt 工程、上下文感知、部署发布等核心环节所有代码均经过生产环境验证。这款工具能显著降低 Java 开发者的机械性工作成本而核心思路也可迁移到其他领域如前端代码生成、测试用例生成。如果对你有帮助欢迎点赞 收藏 关注后续会持续更新 Spring AI 进阶实战内容如私有化部署、本地知识库接入。如果有任何问题或不同见解欢迎在评论区交流