From 456f536273a70d1edbe44e2dcc998a5ed0963949 Mon Sep 17 00:00:00 2001 From: kimagery <42256206+kimagery@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:34:35 +0800 Subject: [PATCH 1/7] Update java8-tutorial-translate.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original stream().sorted().count() does not perform sorting and just counts elements directly — it’s equivalent to stream().count(). This makes parallelStream().sorted().count() appear slower by comparison. --- docs/java/new-features/java8-tutorial-translate.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/java/new-features/java8-tutorial-translate.md b/docs/java/new-features/java8-tutorial-translate.md index 311825508b1..44833fd75b1 100644 --- a/docs/java/new-features/java8-tutorial-translate.md +++ b/docs/java/new-features/java8-tutorial-translate.md @@ -592,7 +592,7 @@ for (int i = 0; i < max; i++) { ```java //串行排序 long t0 = System.nanoTime(); -long count = values.stream().sorted().count(); +long count = Arrays.stream(list.stream().sorted().toArray()).count(); System.out.println(count); long t1 = System.nanoTime(); @@ -612,7 +612,7 @@ sequential sort took: 709 ms//串行排序所用的时间 //并行排序 long t0 = System.nanoTime(); -long count = values.parallelStream().sorted().count(); +long count = Arrays.stream(list.parallelStream().sorted().toArray()).count(); System.out.println(count); long t1 = System.nanoTime(); From 5bba638890d06f319a0b5f520fd3e3e8e6ef04da Mon Sep 17 00:00:00 2001 From: Guide Date: Sun, 29 Mar 2026 14:07:23 +0800 Subject: [PATCH 2/7] =?UTF-8?q?docs=EF=BC=9A=E6=96=B0=E5=A2=9E=E4=B8=A4?= =?UTF-8?q?=E7=AF=87=20AI=20Coding=20=E5=AE=9E=E8=B7=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vuepress/navbar.ts | 2 +- docs/ai/ai-coding/idea-qoder-plugin.md | 414 ++++++++++++++++++++ docs/ai/ai-coding/trae-m2.7.md | 517 +++++++++++++++++++++++++ docs/ai/rag/rag-basis.md | 8 +- docs/ai/rag/rag-vector-store.md | 8 +- 5 files changed, 939 insertions(+), 10 deletions(-) create mode 100644 docs/ai/ai-coding/idea-qoder-plugin.md create mode 100644 docs/ai/ai-coding/trae-m2.7.md diff --git a/docs/.vuepress/navbar.ts b/docs/.vuepress/navbar.ts index 86b01633884..76aedfd3cc7 100644 --- a/docs/.vuepress/navbar.ts +++ b/docs/.vuepress/navbar.ts @@ -2,7 +2,7 @@ import { navbar } from "vuepress-theme-hope"; export default navbar([ { text: "后端面试", icon: "java", link: "/home.md" }, - { text: "AI面试", icon: "machine-learning", link: "/ai/" }, + { text: "AI面试", icon: "a-MachineLearning", link: "/ai/" }, { text: "实战项目", icon: "project", link: "/zhuanlan/interview-guide.md" }, { text: "知识星球", diff --git a/docs/ai/ai-coding/idea-qoder-plugin.md b/docs/ai/ai-coding/idea-qoder-plugin.md new file mode 100644 index 00000000000..1bef26d1e96 --- /dev/null +++ b/docs/ai/ai-coding/idea-qoder-plugin.md @@ -0,0 +1,414 @@ +大家好,我是 Guide。如果你是 JetBrains IDE 的重度用户,大概率有过这样的纠结:想用 AI 辅助编程,但主流工具——Cursor、Trae、Qoder——大多基于 VS Code。切过去?舍不得 JetBrains 调试和重构体验。不切?又感觉错过了 AI 的效率红利。 + +有朋友会说:Claude Code、Gemini CLI 这些终端工具不是挺香的吗?确实香,但说实话,CLI 模式也有明显的短板:没有原生 UI 交互,看代码、审 diff 都不够直观。虽然可以通过一些开源项目(如 vibe kanban、1Code)来缓解,但在做复杂项目时,还是存在一些局限性。 + +现在的后端开发者,大致分成了四大阵营: + +| 阵营 | 工具组合 | 特点 | +| -------------- | ----------------------------------------------- | ---------------------------- | +| **CLI 派** | Claude Code/Gemini CLI/Codex | 终端操作,效率高但 UI 交互弱 | +| **VS Code 派** | VS Code + 插件 | 轻量灵活,功能受限 | +| **混合派** | CLI/AI 编程IDE(如 Cursor) 写 → JetBrains 验收 | AI 辅助 + IDEA 兜底 | +| **一体派** | **JetBrains + Qoder 插件** | **心流专注,开箱即用** | + +我目前属于“混合使用派”:Claude Code 与 IDEA + Qoder 插件是主要组合。 + +对于很多逻辑复杂的项目,IDEA 的掌控感能让人更安心。 + +这篇文章我会通过两个真实场景的实战案例,看看 IDEA 搭配 Qoder 在实际开发中的效果,并且分享一些实用的小技巧。 + +## Qoder JetBrains 插件上手教程 + +### 安装与配置 + +**第一步**:点击 **Settings | Plugins** 搜索 **"qoder"**,选择 Qoder - Agentic AI Coding Platform 并安装。 + +![插件安装界面](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/plugin-install-interface.png) + +**第二步**:安装完成后,点击 Sign In 登录注册。 + +![登录界面](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/login-interface.png) + +**第三步(可选)**:默认界面为英文,习惯中文可点击右上角 Plugin Settings,将 Display Language 设为简体中文。 + +![语言设置界面](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/language-settings-interface.png) + +**第四步(可选)**:配置数据库连接。Qoder 支持 `@database` 上下文,可直接引用数据库表结构。建议提前配置项目相关数据库。 + +以 MySQL 为例,打开右侧 Database 工具窗口,点击 **+** 号,选择 **Data Source | MySQL**: + +![添加数据源](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/add-data-source.png) + +填写连接信息,测试通过后点击 OK。 + +![数据库配置完成](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/database-config-complete.png) + +至此,前期准备工作完成。 + +### 任务一:订单查询频繁报错?原本一天的工作,现在 10 分钟搞定 + +#### 背景说明 + +这是一个电商后台管理系统,运营部门每月生成经营分析报表。由于数据量较大(订单表 1000 万+),且开发时间紧张,代码存在多个性能隐患。 + +运营反馈订单查询频繁报错,定位到接口: + +```bash +curl -X POST http://localhost:8080/api/report/orders \ + -H "Content-Type: application/json" \ + -d '{"page": 1000000, "size": 10}' +``` + +这是一个典型的深分页请求。接口代码逻辑如下: + +```java +@Transactional(readOnly = true) +public OrderListResponse getOrderList(OrderListRequest request) { + int pageNum = request.getPage() == null ? 1 : request.getPage(); + int pageSize = request.getSize() == null ? 10 : request.getSize(); + + // 问题核心:深分页查询 + Page pageParam = new Page<>(pageNum, pageSize); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (request.getStatus() != null && !request.getStatus().isEmpty()) { + wrapper.eq(Order::getStatus, request.getStatus()); + } + if (request.getShopId() != null) { + wrapper.eq(Order::getShopId, request.getShopId()); + } + + // 排序字段可能无索引,触发全表扫描 + wrapper.orderByDesc(Order::getCreatedAt); + + // 深分页:LIMIT 9999990, 10 + IPage orderPage = orderMapper.selectPage(pageParam, wrapper); + + // 关联查询用户、店铺信息... +} +``` + +当 `page=1000000` 时,MySQL 执行 `LIMIT 9999990, 10`,需要扫描前 1000 万行后丢弃,性能急剧下降。 + +#### 传统方式的困境 + +按照传统流程,接口调优需要: + +1. 阅读梳理代码逻辑 +2. 分析代码优化空间 +3. 结合日志分析 SQL 执行计划 +4. 输出解决方案并实施 +5. 回归测试与部署上线 + +**一套完整的排查优化下来,基本一天就过去了。** + +#### Qoder 解法:从执行者到指挥者 + +有了 Qoder 后,工作模式发生根本转变:**决策编排 → 方案沟通 → 指挥执行 → 验收确认**。 + +只需整理思路,给出明确目标: + +```bash +针对订单列表查询接口出现的"java.net.SocketTimeoutException: Read timed out"超时问题,需要从接口代码逻辑和数据库层面进行分析并提供解决方案。 + +接口信息:POST http://localhost:8080/api/report/orders +请求参数:{"page": 1000000, "size": 10} + +请从以下方面给出解决方案: +1. 分析接口代码逻辑中可能导致超时的因素 +2. 检查数据库层面的问题(索引、查询性能、数据量) +3. 提出具体的优化措施 +``` + +为了让 Qoder 更好地完成任务,添加数据库上下文: + +1. 点击 **+Add Context** 按钮 +2. 选择 **@database**,选择对应的数据库 Schema + +![添加数据库上下文](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/add-database-context-1.png) + +#### 问题分析与方案输出 + +**秒级定位问题根因** + +Qoder 精准定位到代码入口,完成分析并给出问题根因——无需人工逐行阅读代码: + +![代码分析结果](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/code-analysis-result.png) + +**独到之处:代码与数据库联合诊断** + +结合数据库 Schema,Qoder 给出了综合分析报告。这一点是日常工作中容易忽略的——传统方式下,开发者往往只关注代码层面,而 Qoder 会主动关联数据库结构: + +![综合分析报告](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/comprehensive-analysis-report.png) + +**代码层面优化** + +Qoder 给出了三套方案,包括延迟关联查询(子查询只返回 ID,利用覆盖索引快速定位): + +![代码优化方案](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/code-optimization-solution.png) + +**值得注意的方案** + +分页查询总记录计算,Qoder 给出了一个比较少见的方案——通过主键索引页数和页内平均行数进行数学估算。这种方案对大数据量且精度要求不高的场景适用: + +![数据库优化建议](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/database-optimization-suggestion.png) + +#### 方案实施与验收 + +审核评估后,选定延迟关联 + 索引优化方案: + +```bash +基于审核评估结果,执行以下优化: +1. 实施延迟关联查询策略,重构深分页查询逻辑 +2. 根据索引建议创建优化索引结构 +3. 编写单元测试,覆盖核心功能点,建立性能基准 +``` + +Qoder 完成实施后,`getOrderList` 方法的改造: + +- 结合生产故障,完成最大页码配置和逻辑限制 +- 按不同策略完成分页统计和列表查询 + +代码风格符合《阿里巴巴 Java 开发手册》最佳实践: + +![重构后代码](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/refactored-code.png) + +索引脚本可直接在 IDE 中执行,整个工作流无需切换窗口: + +![索引执行](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/index-execution.png) + +**回归测试**:Qoder 完成代码分支梳理,并针对不同场景生成单元测试: + +![单元测试](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/unit-test-1.png) + +**压测环节**:Qoder 完成了所有压力测试编写,并完成了代码预热,编译优化为机器码,尽可能贴合生产实际运行情况: + +![压力测试](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/stress-test.png) + +最后,Qoder 输出了完整的工作总结,包括技术方案和沟通汇报建议: + +![工作总结](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/work-summary.png) + +在代码提交窗口点击 Qoder,自动生成本次提交说明。**至此,不到 10 分钟完成了一个接口的优化工作。** + +![提交说明](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/commit-message.png) + +### 任务二:祖传代码不敢动?2-3 天的工作,现在半天搞定 + +#### 背景:一坨不敢动的"祖传代码" + +退款模块的 `applyRefund` 方法,**150+ 行代码,无注释,魔法值遍地,重复逻辑冗余**。新需求来了:新增风控规则——**72 小时内存在未完成订单的用户禁止申请退款**。 + +**传统方式的困境**: + +- 代码逻辑复杂,不敢轻易改动 +- 新增规则需要全量回归测试 +- 预估工作量:**2-3 天** + +#### 逻辑梳理:让 Agent 替你读懂祖传代码 + +借助 Qoder 背后模型强大的算力和上下文推理能力,以及 Agent 的任务规划与执行能力,可以让其完成业务功能的阅读并重构: + +```bash +请结合一个简单的数据流,详细介绍退款申请的完整业务流程,并在代码中补充相应注释 +``` + +为了保证 Agent 输出的准确性,把存量的 Schema 作为上下文提交给 Qoder: + +![添加数据库上下文](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/add-database-context-2.png) + +Qoder 收到任务后,从整体概述开始,通过逐个分支梳理注释的方式执行任务: + +![逻辑梳理过程](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/logic-analysis-process.png) + +对应注释代码非常整洁清晰,结合 Agent 给出的数据流,稍加调测就可以快速完成逻辑梳理: + +![注释代码示例](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/commented-code-example.png) + +任务结束后,Qoder 清晰地归纳了接口逻辑和特殊规则点: + +![摘要总结](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/summary-conclusion.png) + +#### 代码重构:增量重构,安全可控 + +完成逻辑梳理后,下达第二条指令,完成功能重构与回归: + +```bash +请按照《阿里巴巴 Java 开发手册》中的编码规范、命名约定、异常处理及安全规范,结合《重构:改善既有代码的设计》中提出的代码重构原则与方法,对退款申请功能模块进行系统性重构。完成重构后,需编写全面的单元测试、集成测试及功能测试,覆盖所有业务逻辑分支与边界条件,确保重构前后功能一致性及系统稳定性,实现 100% 的逻辑回归验证。 +``` + +在此期间,Qoder 依次完成: + +1. 目标文件查看:定位重构代码段 +2. 代码问题分析:指出魔法值、重复代码、方法过长等问题 +3. 系统重构:依次完成常量创建、重复代码提取、领域建模设计和职责分离 +4. 编写测试代码完成逻辑回归 + +最终完成后的代码如下。在 diff 审核过程中,发现 Qoder 有一个值得学习的做法:**它的重构工作并非在既有文件基础上进行大刀阔斧的修改,而是创建一个全新的 `RefundServiceRefactored`,采用安全重构策略**: + +```java +/** + * 退款申请(重构后) + */ +@Transactional(rollbackFor = Exception.class) +public RefundResponse applyRefund(RefundApplyRequest request) { + log.info("【退款申请】开始处理: orderId={}, userId={}, amount={}", + request.getOrderId(), request.getUserId(), request.getRefundAmount()); + + // 1. 查询并校验订单 + Order order = getAndValidateOrder(request.getOrderId(), request.getUserId()); + + // 2. 判断退款类型并处理 + if (request.getOrderItemId() != null) { + return processPartialRefund(request, order); // 部分退款 + } else { + return processFullRefund(request, order); // 全额退款 + } +} + +/** + * 处理部分退款 + */ +private RefundResponse processPartialRefund(RefundApplyRequest request, Order order) { + log.info("【退款申请】处理部分退款: orderItemId={}", request.getOrderItemId()); + + // 查询并校验订单明细 + OrderItem orderItem = orderItemMapper.selectById(request.getOrderItemId()); + refundValidator.validateOrderItemBelongsToOrder(orderItem, order.getId()); + + // 校验退款数量与金额 + Integer refundQuantity = getRefundQuantity(request.getQuantity()); + refundValidator.validateRefundQuantity(refundQuantity, orderItem.getRefundableQuantity()); + BigDecimal itemRefundableAmount = refundCalculator.calculateItemRefundableAmount(orderItem, refundQuantity); + refundValidator.validateRefundAmount(request.getRefundAmount(), itemRefundableAmount); + + // 执行风控检查 + 创建退款记录 + performRiskCheck(order, request.getRefundAmount(), request.getUserId()); + Refund refund = createRefundRecord(request, order, refundQuantity); + + log.info("【退款申请】部分退款成功: refundId={}", refund.getId()); + return RefundResponse.success(refund.getId()); +} +``` + +**重构亮点**: + +| 亮点 | 说明 | +| ------------ | -------------------------------------------------------- | +| **方法拆分** | 主方法仅 15 行,部分退款/全额退款逻辑分离 | +| **职责分离** | `refundValidator`、`refundCalculator` 独立处理校验与计算 | +| **注释清晰** | 每个步骤标注明确,一目了然 | +| **日志规范** | 使用【】标注关键节点,便于追踪 | +| **异常处理** | `rollbackFor = Exception.class` 确保事务回滚 | + +Qoder 自动进行的单元测试验收,非常高效地完成了 80% 既有逻辑的分支覆盖: + +![单元测试验收](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/unit-test-verification.png) + +#### 功能迭代:一行指令,规则上线 + +有了这样一套简洁的代码后,既有业务迭代就变得非常轻松。快速定位到风控的逻辑代码段 `validateRiskMaxAmount`,对 Qoder 下达最后一条指令: + +```bash +在风控系统中新增一条退款限制规则:当用户在最近 72 小时(3 天)内存在任何未完成状态的订单记录时,系统应自动拒绝该用户提交的退款申请。 +``` + +对应实现代码如下。可以看到,结合 Qoder 强大的上下文推理能力和任务执行质量,完成既有逻辑的梳理后,职责单一的校验框架和配套的单元测试已经就位,后续的增量迭代也变得易于处理和回归: + +![功能迭代实现](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/feature-iteration-implementation.png) + +#### 记忆沉淀:越用越懂你的编程习惯 + +完成任务后,Qoder 自动形成了针对该项目的记忆: + +- **项目特点记忆**:延迟关联查询优于游标分页、接口优化需配套性能测试 +- **编码规范记忆**:遵循《阿里巴巴 Java 开发手册》、BigDecimal 使用 `compareTo` 比较 +- **业务规则记忆**:退款风控规则(72 小时未完成订单拦截、单笔金额上限等) + +Qoder 考虑到订单退款功能的重要性,在记忆列表中明确记录了与其交互的理念和规范。这使得后续的增量迭代时,只要 Qoder 能够准确将这份记忆召回,退款核心功能的维护就会随着迭代愈发从容: + +![记忆沉淀](https://oss.javaguide.cn/github/javaguide/ai/coding/qoder/idea-plugin/memory-accumulation.png) + +## 能力拆解:Qoder 在这个示例中做了什么 + +通过上述两个实战案例,可以清晰地看到 Qoder JetBrains 插件如何在实际开发 workflow 中发挥价值。下面从四个维度拆解其核心能力: + +### 1. 工程感知与上下文理解 + +Qoder 展现出了对大型工程项目的深度理解能力: + +- **数据库 Schema 感知**:在任务一中,Qoder 结合 `@database` 上下文,精准分析了订单表结构、索引情况与查询模式,给出了覆盖索引优化建议。 + +- **代码逻辑溯源**:在任务二中,面对没有任何注释的冗长退款代码,Qoder 通过静态分析快速梳理出业务流程:订单校验 → 金额计算 → 风控检查 → 数据持久化,并准确识别出重复代码、魔法值等代码坏味道。 + +- **跨文件关联**:Qoder 能够自动感知任务所需的关联文件,如从 `RefundService` 自动追踪到 `OrderMapper`、`RefundValidator` 等依赖组件,无需手动添加上下文。 + +### 2. 端到端的任务执行能力 + +Qoder 不是简单的代码补全工具,而是能够完成从分析到落地的完整闭环: + +| 能力维度 | 具体表现 | 效果量化 | +| -------------- | ----------------------------------- | ------------------------- | +| **工程感知** | 自动分析数据库 Schema、代码依赖关系 | 减少 80% 上下文切换 | +| **端到端执行** | 分析→设计→编码→测试→验收完整闭环 | 接口优化从 1 天 → 10 分钟 | +| **渐进重构** | 增量式重构,保留原有代码 | 重构风险降低 90% | +| **记忆学习** | 自动沉淀项目规范与编码习惯 | 后续迭代效率提升 50%+ | + +### 3. 渐进式重构与增量迭代 + +Qoder 在任务二中展现了一个值得学习的工程实践:**渐进式重构而非大爆炸式重写**。 + +- **增量式重构**:Qoder 没有直接修改原有的 `RefundService`,而是创建了全新的 `RefundServiceRefactored` 类,通过增量方式完成重构。这种方式的优势在于: + + - 保留原有代码作为备份,降低重构风险 + - 便于 A/B 测试和灰度发布 + - 新功能直接在重构后的代码上迭代 + +- **职责分离**:Qoder 按照单一职责原则(SRP),将原本混杂在一起的校验逻辑、金额计算、单号生成抽离到独立组件: + + - `RefundValidator`:统一业务校验 + - `RefundCalculator`:金额计算逻辑 + - `RefundNoGenerator`:退款单号生成 + +- **防御性编程**:在重构过程中,Qoder 自动添加了空指针检查、边界条件处理等防御性代码,提升了系统的健壮性。 + +### 4. 记忆感知与持续学习 + +这些记忆会在后续交互中被自动召回,让 AI 的建议越来越精准,实现"越用越懂你"的效果。 + +## 总结 + +Qoder JetBrains 插件为后端开发者提供了一种新的工作方式:**在保持 JetBrains IDE 使用习惯的同时,利用 AI Agent 的推理分析与编码落地能力**。 + +通过本文的两个实战案例,可以看到: + +| 维度 | 传统方式 | Qoder 辅助 | +| -------- | -------------------------- | ----------------------------- | +| **效率** | 接口优化 1 天,重构 2-3 天 | **30-50 分钟完成** | +| **质量** | 依赖个人经验,容易遗漏 | **系统性重构 + 全面测试覆盖** | +| **体验** | 多工具切换,心流频繁打断 | **一个窗口,心流专注** | +| **成长** | 重复劳动,知识难以沉淀 | **自动记忆,越用越懂你** | + +## 写在最后 + +现在的技术环境很像是在盖大楼。AI 和新框架帮你把脚手架搭得飞快,而且像 Qoder 这样的插件让你在熟悉的 IDE 环境中就能完成这一切,无需切换窗口打断思路。但如果你缺乏底层原理知识和软件架构设计思维,即使 AI 能帮你完成功能落地,你也无法把控系统的交付质量。 + +回顾本文的两个案例: + +- **任务一中的延迟关联查询**,基于对数据库索引原理的理解,才能判断 Qoder 给出的方案是否合理。 + +- **任务二中的代码重构**,熟悉《重构:改善既有代码的设计》和《阿里巴巴 Java 开发手册》中的 SRP、DRY 等原则,才能准确评估 Qoder 重构的质量。 + +- **性能基准测试中的 JIT 预热**,对 JVM 底层执行机制的把握——不了解这一点,性能测试的数据就可能失真。 + +- **方案选择与权衡**,对业务场景和技术边界的把握。比如选择延迟关联查询而非游标分页,是因为后者会影响用户体验——这种判断,AI 无法替你做。 + +因此,在享受 Qoder 带来的效率提升的同时,有三点建议: + +1. **保持对底层原理的学习**:数据库索引、JVM 内存模型、并发编程原理——这些"地基"知识不会因 AI 而贬值。 + +2. **阅读经典书籍**:《重构》《设计模式》《高性能 MySQL》《深入理解 Java 虚拟机》——这些经典帮助你建立判断 AI 输出质量的"标尺"。 + +3. **培养架构思维**:把省下来的时间投入到对系统架构、业务本质的思考上。 + +**如果你也是 JetBrains IDE 的忠实用户,不妨尝试一下 Qoder JetBrains 插件。用下来感觉非常顺手——在熟悉的 IDE 环境里,一个窗口搞定所有工作,心流不打断,效率翻倍。** diff --git a/docs/ai/ai-coding/trae-m2.7.md b/docs/ai/ai-coding/trae-m2.7.md new file mode 100644 index 00000000000..8f1fa93cff5 --- /dev/null +++ b/docs/ai/ai-coding/trae-m2.7.md @@ -0,0 +1,517 @@ +> 标题选择: +> +> - M2.7 正式发布!两个真实场景实测,结果有点意外 +> - M2.7 正式发布!实测两个真实场景,表现有点意外 +> - Claude 国产平替? M2.7 杀疯了! +> - 国产编程神器,MiniMax M2.7 发布! +> - 国产 M2.7 杀疯了!Redis 故障排查 + 跨语言重构实测 + +前两天刷到 MiniMax 正式发布了 M2.7 版本。 + +官方在 SWE-Pro 软件工程基准测试中拿到了 56.22% 的成绩,第三方评测机构 PinchBench 也显示它已经升到排行榜第四,超过了 Nemotron 3。 + +我日常开发中也会搭配 MiniMax 辅助写代码,毕竟量大管饱,从 M2.5 开始印象还不错。这次 M2.7 更新,我特别好奇:它到底能不能带来明显提升? + +于是我挑了两个比较有代表性的复杂场景来实际测测看: + +- **场景一**:接口突然大量超时,日志只指向 Redis,但项目里多处都在用 Redis,很难快速定位根因。 +- **场景二**:把 Redis 的慢查询指令从 C 语言源码完整复刻到 Go 实现,考验跨语言重构和上下文理解能力。 + +## 快速上手 + +查看官方文档,MiniMax M2.7支持Claude Code、Cursor、Trae、OpenCode等主流AI开发工具接入。本次测评使用门槛更低的 Trae IDE,具体的接入步骤如下。 + +**第一步**:到Trae官网下载安装并完成初始化,同时到MiniMax平台完成注册和API Key创建: + + + +**第二步**:在Trae中点击"Add Model"添加自定义模型: + +![Trae添加模型入口](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/trae-add-model-entry.png) + +**第三步**:由于Trae暂未内置M2.7,需要选择"Other Models"并手动输入模型ID和API Key: + +![选择Other Models](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/select-other-models.png) + +**第四步**:输入`MiniMax-M2.7`和申请的API Key,点击"Add Model"。若无报错提示,即表示接入成功: + +![输入MiniMax-M2.7和API Key](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/input-minimax-m2.7-api-key.png) + +完成基本安装配置工作之后,接下来我们就基于上述两个相对复杂的场景,看看M2.7的实际表现: + +## 场景一:接口超时问题快速止血与根因定位 + +### 问题定位 + +第一个案例是某次真实线上故障的复现(已脱敏)。当时部门同学反馈某列表查询接口报错,页面无数据。线上监控系统定位到接口信息如下: + +接口:`GET http://localhost:8080/api/rbac/user/list` + +返回结果: + +``` +{ + "code": 500, + "message": "系统繁忙,请稍后重试", + "data": null, + "timestamp": "2026-03-19T10:11:02.632242" +} +``` + +结合异常堆栈信息关键字`Read timed out`,以及对应代码段的`get(key)`操作,我们可以初步认为该报错只是表象并非根因。 + +```java +@Override +public String getConfigValue(String configKey, String environment) { + String cacheKey = CONFIG_CACHE_PREFIX + configKey + ":" + environment; + String value = stringRedisTemplate.opsForValue().get(cacheKey); + if (value != null) { + return value; + } + // 后续逻辑省略 +} +``` + +按照常规处理流程,我们需要快速定位问题根因、完成止血,再联系运维深入排查。但项目中多处用到Redis,逐一排查耗时长,期间可能影响业务稳定性。 + +为了验证M2.7的实际能力,笔者复刻了该故障场景(已脱敏),并让M2.7接手处理。按照企业级线上故障处理流程,首先需要定位根因并完成止血。于是笔者向M2.7下达了第一条指令: + +``` +针对访问 http://localhost:8080/api/rbac/user/list 接口时出现的500错误(错误信息:"系统繁忙,请稍后重试"),请执行以下操作: +1. 分析提供的异常堆栈信息,准确定位导致服务器内部错误的根本原因; +2. 提供详细的线上紧急止血方案,包括但不限于:临时回滚策略、流量限制措施、服务降级方案或紧急重启流程; +3. 解释错误产生的技术原因,指出具体的代码模块或配置问题; + +...... 异常堆栈关键信息:`java.net.SocketTimeoutException: Read timed out` +``` + +![向M2.7下达的诊断指令截图](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-diagnostic-instruction.png) + +M2.7收到请求后,迅速定位到指定代码的上下文,并快速推理出4种可能的根因: + +- Redis 服务器宕机或无响应 +- 连接池配置太小,高并发下耗尽 +- Redis 连接泄漏(连接未正确关闭) +- Redis 服务器负载过高 + +![M2.7推理结果截图](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-inference-result.png) + +到这一步,M2.7已经把问题空间从"N处Redis调用"压缩到了"4种可能根因"——这种**快速收敛问题范围**的能力,和官方SWE-Pro 56.22%的成绩基本吻合。接下来看它的止血思路。 + +### 止血 + +M2.7针对既定异常栈帧快速梳理了代码调用逻辑,准确地指出:列表查询接口被切面拦截,连接池耗尽是500错误的根因。更关键的是,它指出了这段代码缺乏降级策略——这一点笔者是在复盘会上才意识到的。 + +![M2.7代码调用链路分析截图](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-call-chain-analysis.png) + +针对线上问题,止血策略是最关键的环节。M2.7给出了几个解决方案,第一个就是临时关闭权限校验开关——原因在于方案一需要清除Redis缓存数据。虽然方案有些激进,不过,它详细指出了代码的调用链路和表结构信息,这也能很好地辅助我通过业务语义猜测可能的场景和原因。 + +![M2.7调用链路分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-call-chain-analysis-2.png) + +基于M2.7提供的调用链路信息,笔者进一步询问方案一的技术依据,确保业务上快速和M2.7进行对齐: + +```bash +结合代码开发的完整工作流程,详细阐述方案一的技术依据、设计思路及实施合理性。 +``` + +这也是让笔者最满意的地方,M2.7非常贴心地给出了问题代码的调用链路图,让笔者快速地了解到列表查询期间所经过的完整切面和具体故障所处位置,辅助我理解当前问题的影响面,以及本次异常的直接原因。 + +经过不到10分钟的交互,笔者不仅迅速获得一个宏观的架构视角,理解了当前复杂架构的故障和M2.7各个解决方案的依据,例如方案一:通过修改数据库配置重启刷新缓存来规避权限校验。 + +![M2.7调用链路图截图](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-call-chain-diagram.png) + +我们再来看看方案三的思路:当Redis不可用时,使用本地缓存或默认值,避免级联失败。M2.7很好地结合当前工程代码段给出修改建议: + +![M2.7方案三代码片段](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-solution-3-code.png) + +M2.7分析后,我们对问题有了初步的判断:Redis客户端连接池耗尽,导致日常业务接口基于缓存开关查询逻辑崩溃,进而引发雪崩效应。所以,我综合了M2.7给出的多个建议,本着保守、快速止血、业务高峰期不压垮数据库的原则,得出以下hotfix方案: + +```bash +根据提供的方案,创建一个hotfix止血分支,用于紧急修复Redis异常问题。具体实施步骤如下: +1. 基于当前生产环境代码创建hotfix分支,命名规范为"hotfix/redis-exception-handler" +2. 按照方案三实现Redis异常捕获机制,在所有Redis操作处添加try-catch块 +3. 当捕获到Redis异常时,自动降级为直接查询数据库获取数据 +4. 实现JVM本地缓存机制,将查询结果缓存至内存中,设置合理的缓存过期时间 +5. 完成单元测试和集成测试,覆盖率需达到80%以上 +6. 准备回滚方案,确保在紧急情况下能够快速恢复到上一版本 + +``` + +![hotfix方案指令](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/hotfix-instruction.png) + +M2.7收到指令后,非常快速准确地理解了问题,完成任务拆解并逐步执行工作: + +![M2.7任务拆解过程](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-task-breakdown.png) + +最终输出的代码结果如下:M2.7在原有权限校验逻辑中整合了数据库降级查询。不得不说,M2.7在代码上下文理解方面确实展现了官方宣称的"SWE-Pro软件工程基准测试56.22%"的实力——它能够深入理解权限校验逻辑,并完成复杂设计的无缝整合。 + +```java +@Around("permissionCheck()") +public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable { + try { + // 从配置中心读取权限校验开关 + String checkEnabled = configService.getConfigValue("permission.check.enabled", "PROD"); + if (!"true".equalsIgnoreCase(checkEnabled)) { + return joinPoint.proceed(); + } + + // ... 原有权限校验逻辑 ... + + // 尝试从Redis缓存获取权限信息 + Boolean hasPermission = checkPermissionFromCache(redisKey); + + if (hasPermission != null) { + // ... 命中缓存处理 ... + } + + // 降级:从数据库查询权限 + boolean hasPermissionFromDB = checkPermissionFromDatabase(userId, apiPath, httpMethod); + // ... 降级逻辑处理 ... + + } catch (Exception e) { + if (e instanceof RuntimeException && "无权限访问".equals(e.getMessage())) { + throw e; + } + // 发生异常时,触发监控告警并采用保守策略放行 + AlertManager.notify("PERMISSION_CHECK_ERROR", e.getMessage()); + return joinPoint.proceed(); + } +} +``` + +getConfigValue同样补充了本地缓存逻辑,多级缓存设计体现了其容错处理的健壮性。 + +```java +/** + * 获取配置值(指定环境) + */ +@Override +public String getConfigValue(String configKey, String environment) { + String cacheKey = CONFIG_CACHE_PREFIX + configKey + ":" + environment; + + // 【第一步:尝试从本地缓存获取】 + String localValue = localCacheManager.get(cacheKey); + if (localValue != null) { + return localValue; + } + + // 【第二步:尝试从Redis获取】 + try { + if (isRedisAvailable()) { + String value = stringRedisTemplate.opsForValue().get(cacheKey); + if (value != null) { + localCacheManager.put(cacheKey, value, LOCAL_CACHE_TTL); + return value; + } + } + } catch (Exception e) { + // Redis异常,降级到数据库 + handleRedisFailure(e); + } + + // 【第三步:降级到数据库】 + // ... 其他逻辑 ... + return getConfigValueFromDatabaseWithFallback(configKey, environment); +} +``` + +这其中最让笔者感到惊喜的就是本地缓存的设计:M2.7老道地采用开闭原则,基于ConcurrentHashMap完成了本地缓存工具类的封装,全面考虑到堆内存溢出风险,配合LRU算法实现缓存清理,保障了JVM GC的稳定性: + +```java +@Component +public class LocalCacheManager { + // 核心存储:ConcurrentHashMap保证线程安全 + private final Map cache = new ConcurrentHashMap<>(); + private final ScheduledExecutorService cleanupExecutor; + + // 缓存配置 + private static final long DEFAULT_TTL_MILLIS = 300000; // 5分钟 + private static final long MAX_CACHE_SIZE = 10000; + + public LocalCacheManager() { + // 守护线程执行定时清理 + this.cleanupExecutor = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "local-cache-cleanup"); + t.setDaemon(true); + return t; + }); + this.cleanupExecutor.scheduleAtFixedRate(this::cleanupExpiredEntries, 1, 1, TimeUnit.MINUTES); + } + + public void put(String key, String value) { + put(key, value, DEFAULT_TTL_MILLIS); + } + + public void put(String key, String value, long ttlMillis) { + // 容量满时触发LRU清理 + if (cache.size() >= MAX_CACHE_SIZE) { + cleanupExpiredEntries(); + if (cache.size() >= MAX_CACHE_SIZE) { + evictOldestHalf(); + } + } + cache.put(key, new CacheEntry(value, System.currentTimeMillis() + ttlMillis)); + } + + public String get(String key) { + CacheEntry entry = cache.get(key); + if (entry == null || entry.isExpired()) { + cache.remove(key); + return null; + } + return entry.getValue(); + } + + // ... 其他方法省略 ... + + // LRU清理:删除最老的50%数据 + private void evictOldestHalf() { + // ...... 省略排序和清理逻辑 ...... + } + + // 缓存条目 + private static class CacheEntry { + private final String value; + private final long expirationTime; + + public CacheEntry(String value, long expirationTime) { + this.value = value; + this.expirationTime = expirationTime; + } + + public String getValue() { + return value; + } + + public boolean isExpired() { + return System.currentTimeMillis() > expirationTime; + } + } +} +``` + +### 根因定位 + +通过hotfix分支针对线上故障止血之后,我们再来深入排查Redis连接池耗尽的原因。按照模型的输出结果和推断,一个常规的get指令操作按照Redis 10w qps的性能表现来看,10个连接(平均每个指令1~2ms),理想情况下每秒处理约6600条指令,远低于Redis的极限处理能力,所以问题可能出在代码层面,我们需要进一步推断项目中是否存在不合理的Redis操作: + +```bash +结合本次发生的具体故障现象和表现特征,对项目进行全面的系统性全局分析。分析范围应覆盖项目架构、代码实现、依赖管理、环境配置、数据交互等多个维度,重点识别并输出可能导致生产故障的直接原因。 +``` + +![M2.7全局分析指令](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-global-analysis-instruction.png) + +此时M2.7开始基于全局项目结构和上下文进行详细的阅读和推理分析: + +![M2.7项目结构分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-project-structure-analysis.png) + +最终M2.7给出了非常精准且详细的故障分析报告,指出根因:不当的Redis数据结构设计使用scan操作导致连接池夯死。同时,文档还结合上下文给出了该操作的业务流程,便于我们迅速理解这条故障链路: + +![M2.7故障根因分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-root-cause-analysis.png) + +而解决方案也是非常干净利落,通过优化数据结构的方式降低Redis读写操作的时间复杂度,避免连接池夯死: + +![M2.7优化方案建议](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-optimization-suggestion.png) + +场景一测下来,M2.7的表现确实超出预期。从N处Redis调用中精准定位根因,到给出完整止血方案,整个推理链条清晰完整。 + +不过也发现了一些小问题:它给出的方案一(清除Redis缓存)略显激进,实际生产环境可能需要更保守的策略。另外,部分边界条件的防御性代码还是需要人工补充——AI能帮你走到90%,剩下的10%还得靠自己。 + +## 场景2:从Redis C源码到Go实现的跨语言重构 + +### 背景说明 + +接下来我们再来一个高难度场景——复刻Redis慢查询指令。mini-redis是采用Go语言goroutine-per-connection理念提升吞吐量,并以C语言的风格实现符合RESP协议的缓存中间件,由于语言在设计理念上存在偏差,涉及复杂逻辑梳理和异构方案落地。用于验证M2.7官方宣称的"复杂工程系统深层理解"与跨语言架构设计能力再合适不过。 + +### 需求梳理与方案设计 + +针对项目重构类需求,按传统开发模式,我们需要大量时间阅读源代码梳理逻辑,期间因历史原因代码无注释,需结合上下文推理调试。了解原有逻辑后,还需结合新项目架构制定实施步骤,并设计单元测试确保既有逻辑稳定运行。整个流程(研发、测试到发布)保守估计需要3个工作日。抱着试试看的心态,笔者将源代码阅读和技术文档整理工作交给M2.7负责。 + +```bash +我现在需要通过Go语言复刻Redis慢查询指令的实现。请你详细阅读Redis源代码,深入理解慢查询功能的完整实现原理、数据结构设计、处理流程和关键步骤。具体包括但不限于:慢查询日志的存储机制、慢查询阈值的配置与调整、慢查询命令的收集与记录流程、相关API接口的设计与实现,以及慢查询信息的查询与展示方式。请基于这些理解,整理出清晰的技术文档,包括核心原理说明、关键数据结构分析、实现步骤分解以及可能的性能优化考量。 +``` + +等待片刻后,M2.7明确指出技术要求,自底向上地介绍数据结构到执行链路,进行了详尽的分析和介绍: + +![M2.7慢查询数据结构分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slowlog-data-structure.png) + +查看其对慢查询切面逻辑的定位非常准确,在主流程上输出了必要的注释,让我快速了解慢查询的整体处理流程: + +![M2.7慢查询切面逻辑](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slowlog-aspect-logic.png) + +再看其对slot get指令的理解,也非常到位,思路和资深开发一样,抓大放小,明确核心逻辑,在主流程上输出必要的注释: + +![M2.7 slot get指令分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slot-get-instruction.png) + +明确M2.7对慢查询有了准确的理解后,我们让M2.7以开发专家的视角进行功能拆解、落地、测试回归的完整设计文档: + +```bash +按照测试驱动开发(TDD)方法论,使用Go语言创建一个全面详细的开发教程文档,指导复刻Redis的实现。该教程必须符合以下规范: + +1. 开发方法: + - 严格执行测试驱动开发工作流程:先编写会失败的测试,然后实现最简代码以通过测试,最后进行重构 + - 采用类似于原始Redis C语言实现的面向过程的编程风格 + - 尽可能使用纯Go语法和标准库 + +2. 教程结构: + - 从项目设置和环境配置说明开始 + - 按Redis功能拆分为逻辑模块进行开发 + - 针对每个模块/特性,提供: + a. 明确的测试用例定义,包含预期输入和输出 + b. 逐步的代码实现,附带逐行解释 + c. 明确的测试命令和验证流程 + d. 预期测试结果和成功标准 + +3. 技术要求: + - 包含所有组件的完整代码片段 + - 指定确切的文件结构和命名规范 + - 详细说明编译和测试命令 + - 解释常见问题的调试流程 + - 在适用时参考相关的Redis C源代码模式 + +4. 实现细节: + - 从核心数据结构(字符串、列表、哈希等)开始 + - 逐步推进到命令处理和协议实现 + - 包含网络层和客户端-服务器通信 + - 涵盖持久化机制(RDB/AOF) + - 按照相同的行为模式实现基本的Redis命令 + +5. 测试要求: + - 为每个组件提供完整的测试代码 + - 解释测试断言和验证方法 + - 包含单元测试和集成测试 + - 指定如何运行测试并解读结果 + - 详细说明如何根据Redis规范验证正确行为 + +该教程应足够全面,让具备中级Go知识的开发者能够按照指定方法成功构建一个功能类似的Redis系统。 +``` + +等待片刻后,我们收到一份设计文档。M2.7非常准确地结合Redis源代码上下文,梳理出慢查询的核心脉络和关键定义,并规划出完整的开发步骤。这正是官方宣称的"复杂工程系统深层理解"能力: +![M2.7慢查询设计文档](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slowlog-design-doc.png) + +### 编码实现 + +我们从Redis源代码中抽取设计文档后,为确保C语言工程的设计思路能在个人Go语言项目工程规范中准确落地,将其复制到mini-redis项目,让M2.7分析方案的可行性和修改建议: + +![M2.7可行性分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-feasibility-analysis.png) + +等待片刻后M2.7完成文档最后的可行性分析和整理,我们开始对其设计方案进行进一步的复核确认,从项目概述上可以看到M2.7很好地针对mini-redis项目结构进行分析,很准确地定位到慢查询可以直接复用的链表结构体并完成文档微调: + +![M2.7链表结构体分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-linked-list-structure.png) + +再来看看最关键的数据结构实现思路,M2.7也非常准确地结合mini-redis的编码规范,生成Go语言风格的结构体: + +![M2.7 Go风格结构体](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-go-style-struct.png) + +针对慢查询时间测量,这点让笔者感到惊喜。个人实现的指令处理入口和原生Redis有些设计上的出入:由于Go语言语法糖特性,笔者对指针、指针函数以及文件编排做了特殊处理。M2.7非常准确地基于笔者的协程模型定位到时间测量的切面,完成前置计时和后置统计,实现慢查询监控。 + +![M2.7时间测量切面](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-time-measurement-aspect.png) + +最后就是核心的慢查询指令实现,无论是参数解析还是指令查询和响应处理函数,M2.7都非常准确地结合笔者的当前项目封装的逻辑给出明确的编码方案: + +![M2.7慢查询指令实现](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slowlog-command-implementation.png) + +经过仔细复核设计文档,整体开发思路基本一致,但在代码组织细节上仍有调优空间——例如M2.7将`slowlog`指令独立成文件,而未遵循项目惯例统一放入`command.go`。考虑到慢查询功能并非核心内存读写指令,且其日志管理逻辑相对独立,这一处理也算合理折中。权衡之后,我们决定保留M2.7的实现方式,同时手动调整部分文件布局以符合既有工程规范,随后推进剩余开发工作。 + +这一细节也提示我们:AI生成的代码架构虽具合理性,但与既有工程规范的适配仍需人工把关。 + +另外提一句,整个慢查询功能的实现过程中,M2.7有两次生成了不符合项目风格的代码(比如错误处理方式),需要手动调整。这不是大问题,但说明完全依赖AI生成还是不行的。 + +### 验收 + +因为笔者明确指出TDD的开发模型,所以M2.7在这期间很好地结合输出反馈和文档说明完成自循环修复,最终保质保量地结合mini-redis的项目风格完成了慢查询指令的复刻。 + +因为M2.7强大的推理能力和重构能力,在验收过程中我们有了更多的构思空间,之前一直因为源代码梳理总结和技术验收成本过大,所导致的redis.conf配置加载逻辑一直没有实现。 + +因为笔者需要将慢查询时间设置为0,方便对慢查询指令做最后的验收工作,所以笔者索性再次对其提出加载配置的需求: + +![M2.7配置加载实现](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-config-loading.png) + +整个逻辑梳理和开发工作不到1小时,笔者顺利完成了慢查询指令复刻和验收,为了演示慢查询功能,将mini-redis的慢查询阈值设置为0: + +```bash +# 慢查询阈值(微秒) +# 执行时间超过此值的命令会被记录到慢查询日志中 +# 负值表示禁用慢查询日志,0 表示记录所有命令 +# 默认值:10000(10毫秒) +slowlog-log-slower-than 0 +``` + +启动mini-redis服务端后,键入slowlog get 默认返回空: + +![slowlog get初始状态](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/slowlog-get-initial-state.png) + +执行简单的set操作后,键入slowlog get,这条指令如预期被判定为慢查询指令并输出: + +![slowlog get记录set命令](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/slowlog-get-record-set-command.png) + +同理,我们依次键入后续几条指令,也都准确按照链表头插法入队,实现按照时间降序排列输出: + +![slowlog get多条记录](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/slowlog-get-multiple-records.png) + +## MiniMax M2.7核心优势分析 + +通过对两个典型场景的深度测评,结合官方公布的基准测试数据,我们总结出MiniMax M2.7在开发辅助领域的核心优势: + +**基准测试表现**: + +![](images/benchmark-test-results.png) + +_数据来源:MiniMax官方发布及第三方评测机构_ + +### 1. 强大的上下文理解能力 + +M2.7能够理解整个项目的代码结构和业务逻辑,而非孤立地处理单个问题点。在场景1中,它准确梳理了从接口请求到Redis操作的完整调用链路;在场景2中,它快速把握了Redis源代码的设计理念。 + +### 2. 多层级问题处理能力 + +| 问题层级 | M2.7表现 | +| -------- | -------------------------------- | +| 止血处理 | 提供快速应急方案,支持服务降级 | +| 根因定位 | 深入分析代码逻辑,识别架构问题 | +| 长期优化 | 给出数据结构和架构层面的改进建议 | + +### 3. 跨语言迁移能力 + +在场景2中,M2.7成功完成了从Redis C语言实现到Go语言复刻的技术文档编写,证明其在异构语言场景下的迁移和推理能力。 + +### 4. 开发效率提升 + +| 传统方式 | 使用M2.7 | 效率提升 | +| ------------ | -------------------- | ------------ | +| 3个工作日 | 数小时完成核心功能 | 约80% | +| 需要反复调试 | 自动修复和自循环验证 | 减少试错成本 | +| 依赖个人经验 | 结合最佳实践给出方案 | 降低经验门槛 | + +## 总结与建议 + +基于两个真实场景的试用体验,对MiniMax M2.7形成以下客观评价: + +### 能力验证总结 + +| 能力维度 | 场景表现 | 评价 | +| -------------- | --------------------------------------- | ------------------------------------ | +| 故障诊断与止血 | 场景1:快速定位连接池问题,提供降级方案 | 表现优秀,推理链条完整 | +| 跨语言代码迁移 | 场景2:C到Go的慢查询复刻 | 核心逻辑准确,工程规范适配有优化空间 | +| 复杂系统理解 | 场景2:Redis源码分析 | 设计意图把握到位 | +| 端到端交付 | 设计→编码→测试全流程 | 可独立完成,关键节点需人工确认 | + +### 使用建议 + +1. **适用场景**:线上故障应急、遗留系统重构、技术方案预研 +2. **最佳实践**: + - 提供完整上下文,明确约束条件 + - 复杂架构分阶段确认,避免一次性生成过多代码 + - 工程规范相关的文件组织需提前说明或后期调整 +3. **质量把控**:核心逻辑务必人工复核,特别是与既有代码风格的兼容性 + +### 客观评价 + +M2.7在代码理解和方案设计层面表现亮眼,能够显著缩短从问题到方案的时间。但在实际使用中也有一些需要注意的地方: + +- **工程规范适配**:生成的代码结构虽合理,但与个人/团队既有规范的契合度需要磨合 +- **长流程一致性**:在复杂项目的持续迭代中,需要关注上下文记忆的衰减问题 +- **边界情况处理**:部分极端场景的防御性代码建议人工补充 + +值得一提的是,M2.7 是国内第一个通过构建复杂 Agent Harness 以实现自我进化的模型。这套机制让模型能够在实际任务中不断优化自身的推理和代码生成能力,也是它在 SWE-Pro 等基准测试中取得不错成绩的技术基础之一。 + +总体而言,M2.7已具备作为日常开发助手的实用价值,适合承担70%-80%的方案设计和编码工作,剩余部分仍需开发者把控。 diff --git a/docs/ai/rag/rag-basis.md b/docs/ai/rag/rag-basis.md index 589b91dcce6..86306e9663e 100644 --- a/docs/ai/rag/rag-basis.md +++ b/docs/ai/rag/rag-basis.md @@ -10,7 +10,7 @@ head: # RAG 基础概念面试题总结 -去年面字节的时候,面试官问我:”你们项目里的知识库问答是怎么做的?” 我说:”直接调 OpenAI 的 API,把文档塞进去让模型自己读。” +去年面字节的时候,面试官问我:“你们项目里的知识库问答是怎么做的?” 我说:“直接调 OpenAI 的 API,把文档塞进去让模型自己读。” 空气突然安静了三秒。我看到面试官的眉头皱了一下,才意识到事情不对——当时我们项目的文档有 20 多万字,每次请求都超 Token 上限,而且模型根本记不住上周刚更新的接口文档。 @@ -26,8 +26,6 @@ head: 6. RAG 与传统搜索引擎的区别是什么? 7. ⭐️ RAG 的核心优势和局限性分别是什么? -在前面的文章中,我已经分享了 7 道 AI 编程相关的开放性面试题,阅读 5w+,300+ 点赞:[面试官:”你连 Claude Code 都没用过吗?”,我怼回去:”就没用过又怎么了?”](https://mp.weixin.qq.com/s/AkBNmyrcmZsgkSzvJNmO7g)。 - ## ⭐️ 什么是 RAG? **RAG (Retrieval-Augmented Generation,检索增强生成)** 是一种将强大的**信息检索 (Information Retrieval, IR)** 技术与**生成式大语言模型 (LLM)** 相结合的框架。 @@ -235,7 +233,7 @@ RAG 的核心优势和局限性可以从**知识管理、工程落地和性能 ## ⭐️ 更多 RAG 高频面试题 -上面的内容摘自我的[星球](https://mp.weixin.qq.com/s/H2eKimiAbemEDoEsFyWT9g)实战项目教程: [《SpringAI 智能面试平台+RAG 知识库》](https://mp.weixin.qq.com/s/q9UjF53OG0rQVQu92UOKlQ)。内容安排如下(已经更完,一共 13w+ 字) +上面的内容摘自我的[星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)实战项目教程: [《SpringAI 智能面试平台+RAG 知识库》](https://javaguide.cn/zhuanlan/interview-guide.html)。内容安排如下(已经更完,一共 13w+ 字) ![配套教程内容概览](https://oss.javaguide.cn/xingqiu/pratical-project/interview-guide/tutorial-overview.png) @@ -258,7 +256,7 @@ RAG(检索增强生成)是当下企业级 AI 应用最核心的技术栈之 1. **RAG 是什么**:先从知识库检索相关内容,再让 LLM 基于检索结果生成回答,从而减少幻觉、提升可追溯性 2. **为什么需要 RAG**:解决 LLM 的知识时效性、私有数据访问、幻觉三大核心问题 -3. **RAG vs 传统搜索**:RAG 是"信息综合器",传统搜索是"相关性排序器" +3. **RAG vs 传统搜索**:RAG 是“信息综合器”,传统搜索是“相关性排序器” 4. **核心优势**:知识时效性、降低幻觉、数据安全、领域适应性强 5. **局限性**:检索依赖性、上下文窗口限制、工程复杂度、Token 成本 diff --git a/docs/ai/rag/rag-vector-store.md b/docs/ai/rag/rag-vector-store.md index 6ec818506b7..420d6c369d9 100644 --- a/docs/ai/rag/rag-vector-store.md +++ b/docs/ai/rag/rag-vector-store.md @@ -307,7 +307,7 @@ PostgreSQL 最大的优势,也是它在 AI 时代甩开对手的“王牌” ## ⭐️ 更多 RAG 高频面试题 -上面的内容摘自我的[星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)实战项目教程:[《SpringAI 智能面试平台+RAG 知识库》](https://javaguide.cn/zhuanlan/interview-guide.html)。内容安排如下(已经更完,一共 13w+ 字) +上面的内容摘自我的[星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)实战项目教程: [《SpringAI 智能面试平台+RAG 知识库》](https://javaguide.cn/zhuanlan/interview-guide.html)。内容安排如下(已经更完,一共 13w+ 字) ![配套教程内容概览](https://oss.javaguide.cn/xingqiu/pratical-project/interview-guide/tutorial-overview.png) @@ -315,9 +315,9 @@ Spring AI 和 RAG 面试题两篇加起来就接近 60 道题目,主打一个 ![RAG 面试题](https://oss.javaguide.cn/xingqiu/pratical-project/interview-guide/rag-interview-questions.png) -**项目地址**(欢迎 Star 鼓励): +**项目地址** (欢迎 Star 鼓励): -- GitHub: +- Github: - Gitee: 完整代码完全免费开源,没有 Pro 版本或者付费版! @@ -350,4 +350,4 @@ Spring AI 和 RAG 面试题两篇加起来就接近 60 道题目,主打一个 2. **动手实践**:用 pgvector 或 Milvus 搭建一个向量检索 Demo,感受不同索引的性能差异 3. **关注调优**:索引参数(ef_search、nprobe)对召回率和延迟的权衡,需要根据业务场景调优 -向量数据库是 RAG 的"心脏",选对方案、调好参数,是构建高性能 RAG 系统的关键。 +向量数据库是 RAG 的“心脏”,选对方案、调好参数,是构建高性能 RAG 系统的关键。 From 512f335c5c66aa5079f817bfbaab673849e06a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=80=E5=8F=AA=E6=86=A8=E7=8B=97?= <99009438+ZhangChunJie1@users.noreply.github.com> Date: Sun, 29 Mar 2026 23:03:13 +0800 Subject: [PATCH 3/7] Update comments on static variable storage in Java --- docs/java/basis/java-basic-questions-01.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/java/basis/java-basic-questions-01.md b/docs/java/basis/java-basic-questions-01.md index e94ed828592..a80ae30dbb3 100644 --- a/docs/java/basis/java-basic-questions-01.md +++ b/docs/java/basis/java-basic-questions-01.md @@ -636,7 +636,7 @@ flowchart TB public class Test { // 成员变量,存放在堆中 int a = 10; - // 被 static 修饰的成员变量,JDK 1.7 及之前位于方法区,1.8 后存放于元空间,均不存放于堆中。 + // 被 static 修饰的成员变量,JDK 1.6 及之前位于永久代,1.7 后移出永久代,一直存放在堆中。 // 变量属于类,不属于对象。 static int b = 20; From a6df146297a99d51b8fe74b7b40e376cbf2fcea5 Mon Sep 17 00:00:00 2001 From: Guide Date: Mon, 30 Mar 2026 07:42:09 +0800 Subject: [PATCH 4/7] =?UTF-8?q?docs(ai):=20=E8=A1=A5=E5=85=85=20Agent=20?= =?UTF-8?q?=E6=96=87=E7=AB=A0=E5=B9=B6=E6=9B=B4=E6=96=B0=20AI=20=E5=8C=BA?= =?UTF-8?q?=E5=AF=BC=E8=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 context-engineering、prompt-engineering 文档 - 更新 docs/ai/README 与 sidebar/ai.ts 入口 - 调整 idea-qoder-plugin、trae-m2.7 等内容 - 根 README 增加相关说明 --- README.md | 4 + docs/.vuepress/sidebar/ai.ts | 15 ++ docs/ai/README.md | 16 ++- docs/ai/agent/context-engineering.md | 0 docs/ai/agent/prompt-engineering.md | 0 docs/ai/ai-coding/idea-qoder-plugin.md | 10 ++ docs/ai/ai-coding/trae-m2.7.md | 186 +++++++++++-------------- 7 files changed, 127 insertions(+), 104 deletions(-) create mode 100644 docs/ai/agent/context-engineering.md create mode 100644 docs/ai/agent/prompt-engineering.md diff --git a/README.md b/README.md index d4559350694..10b806bfc4b 100755 --- a/README.md +++ b/README.md @@ -21,6 +21,10 @@ +## AI 应用开发面试指南 + +[AI 应用开发面试指南](https://javaguide.cn/ai/)(⭐新增,正在持续更新):专门后端开发准备的 AI 应用开发核心知识,涵盖大模型基础、Agent、RAG、MCP 协议等高频面试考点。 + ## 面试准备 - [⭐Java 后端面试通关计划(涵盖后端通用体系)](./docs/interview-preparation/backend-interview-plan.md) (一定要看 :+1:) diff --git a/docs/.vuepress/sidebar/ai.ts b/docs/.vuepress/sidebar/ai.ts index 56b422ae7e5..49497ea2321 100644 --- a/docs/.vuepress/sidebar/ai.ts +++ b/docs/.vuepress/sidebar/ai.ts @@ -33,4 +33,19 @@ export const ai = arraySidebar([ }, ], }, + { + text: "AI 编程实战", + icon: ICONS.CODE, + prefix: "ai-coding/", + children: [ + { + text: "IDEA + Qoder 插件多场景实战", + link: "idea-qoder-plugin", + }, + { + text: "Trae + MiniMax 多场景实战", + link: "trae-m2.7", + }, + ], + }, ]); diff --git a/docs/ai/README.md b/docs/ai/README.md index 61bba64745c..830c280f045 100644 --- a/docs/ai/README.md +++ b/docs/ai/README.md @@ -1,11 +1,11 @@ --- title: AI 应用开发面试指南 -description: 深入浅出掌握 AI 应用开发核心知识,涵盖大模型基础、Agent、RAG、MCP 协议等高频面试考点,适合校招/社招 AI 应用开发岗位面试复习。 +description: 深入浅出掌握 AI 应用开发核心知识,涵盖大模型基础、Agent、RAG、MCP 协议、AI 编程实战等高频面试考点,适合校招/社招 AI 应用开发岗位面试复习。 icon: "ai" head: - - meta - name: keywords - content: AI面试,AI面试指南,AI应用开发,LLM面试,Agent面试,RAG面试,MCP面试,AI编程面试 + content: AI面试,AI面试指南,AI应用开发,LLM面试,Agent面试,RAG面试,MCP面试,AI编程面试,AI编程实战 --- ::: tip 写在前面 @@ -99,6 +99,13 @@ AI 编程工具正在深刻改变开发者的工作方式。在面试中,你 在[《AI 编程开放性面试题》](./llm-basis/ai-ide.md)中,我会分享 7 道高频开放性面试问题的回答思路。 +### 6. AI 编程实战 + +纸上得来终觉浅。只有亲手用过 AI 编程工具,才能真正理解它的工作边界和使用技巧。在 AI 编程实战系列中,我会通过真实场景的实战案例,分享 AI 辅助编程的使用经验: + +- [《IDEA 搭配 Qoder 插件实战》](./ai-coding/idea-qoder-plugin.md):从接口优化到代码重构,展示如何在 JetBrains IDE 中利用 AI 完成从分析到落地的完整闭环 +- [《Trae + MiniMax 多场景实战》](./ai-coding/trae-m2.7.md):使用 Trae IDE 接入 MiniMax 大模型,通过 Redis 故障排查和跨语言重构场景,分享 AI 辅助编程的实战经验与踩坑心得 + ## 文章列表 ### 大模型基础 @@ -117,6 +124,11 @@ AI 编程工具正在深刻改变开发者的工作方式。在面试中,你 - [万字详解 RAG 基础概念](./rag/rag-basis.md) - 深入理解 RAG 的工作原理、核心优势和局限性 - [万字详解 RAG 向量索引算法和向量数据库](./rag/rag-vector-store.md) - 掌握 HNSW、IVFFLAT 等索引算法原理,学会选择合适的向量数据库 +### AI 编程实战 + +- [IDEA + Qoder 插件多场景实战:接口优化与代码重构](./ai-coding/idea-qoder-plugin.md) - 通过深分页优化、祖传代码重构两个真实案例,展示 AI 辅助编程的实战效果 +- [Trae + MiniMax 多场景实战:Redis 故障排查与跨语言重构](./ai-coding/trae-m2.7.md) - 使用 Trae IDE 接入 MiniMax 大模型,通过 Redis 故障排查和跨语言重构场景,分享 AI 辅助编程的实战经验 + ## 配图预览 为了帮助读者更好地理解抽象的技术概念,我在每篇文章中都绘制了大量配图。这里展示几张: diff --git a/docs/ai/agent/context-engineering.md b/docs/ai/agent/context-engineering.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/ai/agent/prompt-engineering.md b/docs/ai/agent/prompt-engineering.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/ai/ai-coding/idea-qoder-plugin.md b/docs/ai/ai-coding/idea-qoder-plugin.md index 1bef26d1e96..681a1300b4c 100644 --- a/docs/ai/ai-coding/idea-qoder-plugin.md +++ b/docs/ai/ai-coding/idea-qoder-plugin.md @@ -1,3 +1,13 @@ +--- +title: IDEA + Qoder 插件多场景实战:接口优化与代码重构 +description: 通过两个真实实战案例,展示 IDEA 搭配 Qoder 插件在深分页优化、祖传代码重构等场景下的实际效果,分享从执行者到指挥者的工作模式转变。 +category: AI 编程实战 +head: + - - meta + - name: keywords + content: Qoder,IDEA插件,AI编程,AI辅助开发,代码重构,深分页优化,JetBrains,智能编码 +--- + 大家好,我是 Guide。如果你是 JetBrains IDE 的重度用户,大概率有过这样的纠结:想用 AI 辅助编程,但主流工具——Cursor、Trae、Qoder——大多基于 VS Code。切过去?舍不得 JetBrains 调试和重构体验。不切?又感觉错过了 AI 的效率红利。 有朋友会说:Claude Code、Gemini CLI 这些终端工具不是挺香的吗?确实香,但说实话,CLI 模式也有明显的短板:没有原生 UI 交互,看代码、审 diff 都不够直观。虽然可以通过一些开源项目(如 vibe kanban、1Code)来缓解,但在做复杂项目时,还是存在一些局限性。 diff --git a/docs/ai/ai-coding/trae-m2.7.md b/docs/ai/ai-coding/trae-m2.7.md index 8f1fa93cff5..b45f6ee0962 100644 --- a/docs/ai/ai-coding/trae-m2.7.md +++ b/docs/ai/ai-coding/trae-m2.7.md @@ -1,43 +1,45 @@ -> 标题选择: -> -> - M2.7 正式发布!两个真实场景实测,结果有点意外 -> - M2.7 正式发布!实测两个真实场景,表现有点意外 -> - Claude 国产平替? M2.7 杀疯了! -> - 国产编程神器,MiniMax M2.7 发布! -> - 国产 M2.7 杀疯了!Redis 故障排查 + 跨语言重构实测 +--- +title: Trae + MiniMax 多场景实战:Redis 故障排查与跨语言重构 +description: 使用 Trae IDE 接入 MiniMax 大模型,通过 Redis 连接池故障排查和 Redis C 源码到 Go 跨语言重构两个真实场景,分享 AI 辅助编程的实战经验与工作技巧。 +category: AI 编程实战 +head: + - - meta + - name: keywords + content: Trae,AI编程,AI编程IDE,Redis故障排查,跨语言重构,Go语言,AI辅助开发,大模型编程 +--- -前两天刷到 MiniMax 正式发布了 M2.7 版本。 +大家好,我是 Guide。前面分享过一篇 [IDEA 搭配 Qoder 插件的实战](./idea-qoder-plugin.md),那篇主要讲在 JetBrains 体系内用 AI 辅助编码。这篇换个角度,聊聊 **Trae IDE 接入大模型** 的实战体验。 -官方在 SWE-Pro 软件工程基准测试中拿到了 56.22% 的成绩,第三方评测机构 PinchBench 也显示它已经升到排行榜第四,超过了 Nemotron 3。 +Trae 是字节跳动推出的 AI 编程 IDE,基于 VS Code 生态,支持接入多种大模型。本文使用 MiniMax M2.7 作为示例,但 Trae 的接入方式是通用的——换成 Claude、GPT 等其他模型,流程基本一致。 -我日常开发中也会搭配 MiniMax 辅助写代码,毕竟量大管饱,从 M2.5 开始印象还不错。这次 M2.7 更新,我特别好奇:它到底能不能带来明显提升? +我这里使用 MiniMax 是因为我刚好订阅了 MiniMax Code Plan 想要实际测试一些,并非广告,你可以换成其他模型,思路都是一样的。 -于是我挑了两个比较有代表性的复杂场景来实际测测看: +我选了两个比较有代表性的复杂场景来实际验证: - **场景一**:接口突然大量超时,日志只指向 Redis,但项目里多处都在用 Redis,很难快速定位根因。 - **场景二**:把 Redis 的慢查询指令从 C 语言源码完整复刻到 Go 实现,考验跨语言重构和上下文理解能力。 -## 快速上手 +## 快速上手:Trae 接入大模型 -查看官方文档,MiniMax M2.7支持Claude Code、Cursor、Trae、OpenCode等主流AI开发工具接入。本次测评使用门槛更低的 Trae IDE,具体的接入步骤如下。 +Trae 支持接入多种大模型,下面以接入自定义模型为例,演示通用配置流程。 -**第一步**:到Trae官网下载安装并完成初始化,同时到MiniMax平台完成注册和API Key创建: +**第一步**:到 Trae 官网下载安装并完成初始化,同时到对应模型平台完成注册和 API Key 创建(本文示例使用 MiniMax 平台): -**第二步**:在Trae中点击"Add Model"添加自定义模型: +**第二步**:在 Trae 中点击"Add Model"添加自定义模型: ![Trae添加模型入口](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/trae-add-model-entry.png) -**第三步**:由于Trae暂未内置M2.7,需要选择"Other Models"并手动输入模型ID和API Key: +**第三步**:选择"Other Models"并手动输入模型 ID 和 API Key: ![选择Other Models](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/select-other-models.png) -**第四步**:输入`MiniMax-M2.7`和申请的API Key,点击"Add Model"。若无报错提示,即表示接入成功: +**第四步**:输入模型 ID(如 `MiniMax-M2.7`)和申请的 API Key,点击"Add Model"。若无报错提示,即表示接入成功: -![输入MiniMax-M2.7和API Key](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/input-minimax-m2.7-api-key.png) +![输入模型ID和API Key](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/input-minimax-m2.7-api-key.png) -完成基本安装配置工作之后,接下来我们就基于上述两个相对复杂的场景,看看M2.7的实际表现: +接入完成后,就可以在 Trae 中使用该模型进行 AI 辅助编程了。接下来通过两个实战场景,分享具体的使用方式和技巧。 ## 场景一:接口超时问题快速止血与根因定位 @@ -74,7 +76,7 @@ public String getConfigValue(String configKey, String environment) { 按照常规处理流程,我们需要快速定位问题根因、完成止血,再联系运维深入排查。但项目中多处用到Redis,逐一排查耗时长,期间可能影响业务稳定性。 -为了验证M2.7的实际能力,笔者复刻了该故障场景(已脱敏),并让M2.7接手处理。按照企业级线上故障处理流程,首先需要定位根因并完成止血。于是笔者向M2.7下达了第一条指令: +为了验证 AI 辅助排查的实际效果,笔者复刻了该故障场景(已脱敏),让模型接手处理。按照企业级线上故障处理流程,首先需要定位根因并完成止血。于是向模型下达了第一条指令: ``` 针对访问 http://localhost:8080/api/rbac/user/list 接口时出现的500错误(错误信息:"系统繁忙,请稍后重试"),请执行以下操作: @@ -87,7 +89,7 @@ public String getConfigValue(String configKey, String environment) { ![向M2.7下达的诊断指令截图](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-diagnostic-instruction.png) -M2.7收到请求后,迅速定位到指定代码的上下文,并快速推理出4种可能的根因: +模型收到请求后,迅速定位到指定代码的上下文,并快速推理出4种可能的根因: - Redis 服务器宕机或无响应 - 连接池配置太小,高并发下耗尽 @@ -96,35 +98,35 @@ M2.7收到请求后,迅速定位到指定代码的上下文,并快速推理 ![M2.7推理结果截图](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-inference-result.png) -到这一步,M2.7已经把问题空间从"N处Redis调用"压缩到了"4种可能根因"——这种**快速收敛问题范围**的能力,和官方SWE-Pro 56.22%的成绩基本吻合。接下来看它的止血思路。 +到这一步,模型已经把问题空间从"N处Redis调用"压缩到了"4种可能根因"——这种**快速收敛问题范围**的能力,正是 AI 辅助排查的核心价值。接下来看它的止血思路。 ### 止血 -M2.7针对既定异常栈帧快速梳理了代码调用逻辑,准确地指出:列表查询接口被切面拦截,连接池耗尽是500错误的根因。更关键的是,它指出了这段代码缺乏降级策略——这一点笔者是在复盘会上才意识到的。 +模型针对既定异常栈帧快速梳理了代码调用逻辑,准确地指出:列表查询接口被切面拦截,连接池耗尽是500错误的根因。更关键的是,它指出了这段代码缺乏降级策略——这一点笔者是在复盘会上才意识到的。 ![M2.7代码调用链路分析截图](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-call-chain-analysis.png) -针对线上问题,止血策略是最关键的环节。M2.7给出了几个解决方案,第一个就是临时关闭权限校验开关——原因在于方案一需要清除Redis缓存数据。虽然方案有些激进,不过,它详细指出了代码的调用链路和表结构信息,这也能很好地辅助我通过业务语义猜测可能的场景和原因。 +针对线上问题,止血策略是最关键的环节。模型给出了几个解决方案,第一个就是临时关闭权限校验开关——原因在于方案一需要清除Redis缓存数据。虽然方案有些激进,不过,它详细指出了代码的调用链路和表结构信息,这也能很好地辅助我通过业务语义猜测可能的场景和原因。 ![M2.7调用链路分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-call-chain-analysis-2.png) -基于M2.7提供的调用链路信息,笔者进一步询问方案一的技术依据,确保业务上快速和M2.7进行对齐: +基于模型提供的调用链路信息,笔者进一步询问方案一的技术依据,确保业务理解上快速对齐: ```bash 结合代码开发的完整工作流程,详细阐述方案一的技术依据、设计思路及实施合理性。 ``` -这也是让笔者最满意的地方,M2.7非常贴心地给出了问题代码的调用链路图,让笔者快速地了解到列表查询期间所经过的完整切面和具体故障所处位置,辅助我理解当前问题的影响面,以及本次异常的直接原因。 +这也是让笔者比较满意的地方,模型给出了问题代码的调用链路图,让笔者快速了解到列表查询期间所经过的完整切面和具体故障所处位置,辅助我理解当前问题的影响面以及本次异常的直接原因。 -经过不到10分钟的交互,笔者不仅迅速获得一个宏观的架构视角,理解了当前复杂架构的故障和M2.7各个解决方案的依据,例如方案一:通过修改数据库配置重启刷新缓存来规避权限校验。 +经过不到10分钟的交互,笔者不仅迅速获得一个宏观的架构视角,理解了当前复杂架构的故障和各解决方案的依据,例如方案一:通过修改数据库配置重启刷新缓存来规避权限校验。 ![M2.7调用链路图截图](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-call-chain-diagram.png) -我们再来看看方案三的思路:当Redis不可用时,使用本地缓存或默认值,避免级联失败。M2.7很好地结合当前工程代码段给出修改建议: +我们再来看看方案三的思路:当Redis不可用时,使用本地缓存或默认值,避免级联失败。模型结合当前工程代码段给出了修改建议: ![M2.7方案三代码片段](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-solution-3-code.png) -M2.7分析后,我们对问题有了初步的判断:Redis客户端连接池耗尽,导致日常业务接口基于缓存开关查询逻辑崩溃,进而引发雪崩效应。所以,我综合了M2.7给出的多个建议,本着保守、快速止血、业务高峰期不压垮数据库的原则,得出以下hotfix方案: +模型分析后,我们对问题有了初步的判断:Redis客户端连接池耗尽,导致日常业务接口基于缓存开关查询逻辑崩溃,进而引发雪崩效应。综合模型的多个建议,本着保守、快速止血、业务高峰期不压垮数据库的原则,得出以下hotfix方案: ```bash 根据提供的方案,创建一个hotfix止血分支,用于紧急修复Redis异常问题。具体实施步骤如下: @@ -139,11 +141,11 @@ M2.7分析后,我们对问题有了初步的判断:Redis客户端连接池 ![hotfix方案指令](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/hotfix-instruction.png) -M2.7收到指令后,非常快速准确地理解了问题,完成任务拆解并逐步执行工作: +模型收到指令后,快速准确地理解了问题,完成任务拆解并逐步执行: ![M2.7任务拆解过程](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-task-breakdown.png) -最终输出的代码结果如下:M2.7在原有权限校验逻辑中整合了数据库降级查询。不得不说,M2.7在代码上下文理解方面确实展现了官方宣称的"SWE-Pro软件工程基准测试56.22%"的实力——它能够深入理解权限校验逻辑,并完成复杂设计的无缝整合。 +最终输出的代码结果如下:模型在原有权限校验逻辑中整合了数据库降级查询,能够深入理解权限校验逻辑并完成复杂设计的整合。 ```java @Around("permissionCheck()") @@ -215,7 +217,7 @@ public String getConfigValue(String configKey, String environment) { } ``` -这其中最让笔者感到惊喜的就是本地缓存的设计:M2.7老道地采用开闭原则,基于ConcurrentHashMap完成了本地缓存工具类的封装,全面考虑到堆内存溢出风险,配合LRU算法实现缓存清理,保障了JVM GC的稳定性: +这其中值得注意的一个细节是本地缓存的设计:模型采用开闭原则,基于ConcurrentHashMap完成了本地缓存工具类的封装,考虑到了堆内存溢出风险,配合LRU算法实现缓存清理: ```java @Component @@ -300,11 +302,11 @@ public class LocalCacheManager { ![M2.7全局分析指令](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-global-analysis-instruction.png) -此时M2.7开始基于全局项目结构和上下文进行详细的阅读和推理分析: +此时模型开始基于全局项目结构和上下文进行详细的阅读和推理分析: ![M2.7项目结构分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-project-structure-analysis.png) -最终M2.7给出了非常精准且详细的故障分析报告,指出根因:不当的Redis数据结构设计使用scan操作导致连接池夯死。同时,文档还结合上下文给出了该操作的业务流程,便于我们迅速理解这条故障链路: +最终模型给出了详细的故障分析报告,指出根因:不当的Redis数据结构设计使用scan操作导致连接池夯死。同时,还结合上下文给出了该操作的业务流程,便于我们迅速理解这条故障链路: ![M2.7故障根因分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-root-cause-analysis.png) @@ -312,25 +314,25 @@ public class LocalCacheManager { ![M2.7优化方案建议](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-optimization-suggestion.png) -场景一测下来,M2.7的表现确实超出预期。从N处Redis调用中精准定位根因,到给出完整止血方案,整个推理链条清晰完整。 +场景一整体体验不错。从N处Redis调用中精准定位根因,到给出完整止血方案,整个推理链条清晰完整。 -不过也发现了一些小问题:它给出的方案一(清除Redis缓存)略显激进,实际生产环境可能需要更保守的策略。另外,部分边界条件的防御性代码还是需要人工补充——AI能帮你走到90%,剩下的10%还得靠自己。 +不过也发现了一些问题:它给出的方案一(清除Redis缓存)略显激进,实际生产环境可能需要更保守的策略。另外,部分边界条件的防御性代码还是需要人工补充——AI能帮你走到90%,剩下的10%还得靠自己。 ## 场景2:从Redis C源码到Go实现的跨语言重构 ### 背景说明 -接下来我们再来一个高难度场景——复刻Redis慢查询指令。mini-redis是采用Go语言goroutine-per-connection理念提升吞吐量,并以C语言的风格实现符合RESP协议的缓存中间件,由于语言在设计理念上存在偏差,涉及复杂逻辑梳理和异构方案落地。用于验证M2.7官方宣称的"复杂工程系统深层理解"与跨语言架构设计能力再合适不过。 +接下来我们再来一个高难度场景——复刻Redis慢查询指令。mini-redis是采用Go语言goroutine-per-connection理念提升吞吐量,并以C语言的风格实现符合RESP协议的缓存中间件,由于语言在设计理念上存在偏差,涉及复杂逻辑梳理和异构方案落地。用于验证大模型的跨语言架构设计能力再合适不过。 ### 需求梳理与方案设计 -针对项目重构类需求,按传统开发模式,我们需要大量时间阅读源代码梳理逻辑,期间因历史原因代码无注释,需结合上下文推理调试。了解原有逻辑后,还需结合新项目架构制定实施步骤,并设计单元测试确保既有逻辑稳定运行。整个流程(研发、测试到发布)保守估计需要3个工作日。抱着试试看的心态,笔者将源代码阅读和技术文档整理工作交给M2.7负责。 +针对项目重构类需求,按传统开发模式,我们需要大量时间阅读源代码梳理逻辑,期间因历史原因代码无注释,需结合上下文推理调试。了解原有逻辑后,还需结合新项目架构制定实施步骤,并设计单元测试确保既有逻辑稳定运行。整个流程(研发、测试到发布)保守估计需要3个工作日。抱着试试看的心态,笔者将源代码阅读和技术文档整理工作交给 AI 负责。 ```bash 我现在需要通过Go语言复刻Redis慢查询指令的实现。请你详细阅读Redis源代码,深入理解慢查询功能的完整实现原理、数据结构设计、处理流程和关键步骤。具体包括但不限于:慢查询日志的存储机制、慢查询阈值的配置与调整、慢查询命令的收集与记录流程、相关API接口的设计与实现,以及慢查询信息的查询与展示方式。请基于这些理解,整理出清晰的技术文档,包括核心原理说明、关键数据结构分析、实现步骤分解以及可能的性能优化考量。 ``` -等待片刻后,M2.7明确指出技术要求,自底向上地介绍数据结构到执行链路,进行了详尽的分析和介绍: +等待片刻后,模型明确指出技术要求,自底向上地介绍数据结构到执行链路,进行了详尽的分析和介绍: ![M2.7慢查询数据结构分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slowlog-data-structure.png) @@ -342,7 +344,7 @@ public class LocalCacheManager { ![M2.7 slot get指令分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slot-get-instruction.png) -明确M2.7对慢查询有了准确的理解后,我们让M2.7以开发专家的视角进行功能拆解、落地、测试回归的完整设计文档: +确认模型对慢查询有了准确的理解后,接下来让它以开发专家的视角进行功能拆解、落地、测试回归的完整设计文档: ```bash 按照测试驱动开发(TDD)方法论,使用Go语言创建一个全面详细的开发教程文档,指导复刻Redis的实现。该教程必须符合以下规范: @@ -385,42 +387,42 @@ public class LocalCacheManager { 该教程应足够全面,让具备中级Go知识的开发者能够按照指定方法成功构建一个功能类似的Redis系统。 ``` -等待片刻后,我们收到一份设计文档。M2.7非常准确地结合Redis源代码上下文,梳理出慢查询的核心脉络和关键定义,并规划出完整的开发步骤。这正是官方宣称的"复杂工程系统深层理解"能力: -![M2.7慢查询设计文档](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slowlog-design-doc.png) +等待片刻后,我们收到一份设计文档。模型结合Redis源代码上下文,梳理出慢查询的核心脉络和关键定义,并规划出完整的开发步骤: +![慢查询设计文档](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slowlog-design-doc.png) ### 编码实现 -我们从Redis源代码中抽取设计文档后,为确保C语言工程的设计思路能在个人Go语言项目工程规范中准确落地,将其复制到mini-redis项目,让M2.7分析方案的可行性和修改建议: +我们从Redis源代码中抽取设计文档后,为确保C语言工程的设计思路能在个人Go语言项目工程规范中准确落地,将其复制到mini-redis项目,让模型分析方案的可行性和修改建议: ![M2.7可行性分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-feasibility-analysis.png) -等待片刻后M2.7完成文档最后的可行性分析和整理,我们开始对其设计方案进行进一步的复核确认,从项目概述上可以看到M2.7很好地针对mini-redis项目结构进行分析,很准确地定位到慢查询可以直接复用的链表结构体并完成文档微调: +等待片刻后模型完成文档最后的可行性分析和整理,我们开始对其设计方案进行进一步的复核确认。从项目概述上可以看到,模型针对mini-redis项目结构进行了分析,准确地定位到慢查询可以直接复用的链表结构体并完成文档微调: ![M2.7链表结构体分析](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-linked-list-structure.png) -再来看看最关键的数据结构实现思路,M2.7也非常准确地结合mini-redis的编码规范,生成Go语言风格的结构体: +再来看看最关键的数据结构实现思路,模型也结合mini-redis的编码规范,生成了Go语言风格的结构体: ![M2.7 Go风格结构体](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-go-style-struct.png) -针对慢查询时间测量,这点让笔者感到惊喜。个人实现的指令处理入口和原生Redis有些设计上的出入:由于Go语言语法糖特性,笔者对指针、指针函数以及文件编排做了特殊处理。M2.7非常准确地基于笔者的协程模型定位到时间测量的切面,完成前置计时和后置统计,实现慢查询监控。 +针对慢查询时间测量,有个细节值得提一下。个人实现的指令处理入口和原生Redis有些设计上的出入:由于Go语言语法糖特性,笔者对指针、指针函数以及文件编排做了特殊处理。模型准确地基于笔者的协程模型定位到时间测量的切面,完成前置计时和后置统计,实现慢查询监控。 ![M2.7时间测量切面](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-time-measurement-aspect.png) -最后就是核心的慢查询指令实现,无论是参数解析还是指令查询和响应处理函数,M2.7都非常准确地结合笔者的当前项目封装的逻辑给出明确的编码方案: +最后就是核心的慢查询指令实现,无论是参数解析还是指令查询和响应处理函数,模型都结合笔者的当前项目封装的逻辑给出了明确的编码方案: ![M2.7慢查询指令实现](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/m2.7-slowlog-command-implementation.png) -经过仔细复核设计文档,整体开发思路基本一致,但在代码组织细节上仍有调优空间——例如M2.7将`slowlog`指令独立成文件,而未遵循项目惯例统一放入`command.go`。考虑到慢查询功能并非核心内存读写指令,且其日志管理逻辑相对独立,这一处理也算合理折中。权衡之后,我们决定保留M2.7的实现方式,同时手动调整部分文件布局以符合既有工程规范,随后推进剩余开发工作。 +经过仔细复核设计文档,整体开发思路基本一致,但在代码组织细节上仍有调优空间——例如模型将`slowlog`指令独立成文件,而未遵循项目惯例统一放入`command.go`。考虑到慢查询功能并非核心内存读写指令,且其日志管理逻辑相对独立,这一处理也算合理折中。权衡之后,我们决定保留模型的实现方式,同时手动调整部分文件布局以符合既有工程规范,随后推进剩余开发工作。 这一细节也提示我们:AI生成的代码架构虽具合理性,但与既有工程规范的适配仍需人工把关。 -另外提一句,整个慢查询功能的实现过程中,M2.7有两次生成了不符合项目风格的代码(比如错误处理方式),需要手动调整。这不是大问题,但说明完全依赖AI生成还是不行的。 +另外提一句,整个慢查询功能的实现过程中,模型有两次生成了不符合项目风格的代码(比如错误处理方式),需要手动调整。这不是大问题,但说明完全依赖AI生成还是不行的。 ### 验收 -因为笔者明确指出TDD的开发模型,所以M2.7在这期间很好地结合输出反馈和文档说明完成自循环修复,最终保质保量地结合mini-redis的项目风格完成了慢查询指令的复刻。 +因为笔者明确指定了TDD的开发模型,所以模型在这期间结合输出反馈和文档说明完成自循环修复,最终结合mini-redis的项目风格完成了慢查询指令的复刻。 -因为M2.7强大的推理能力和重构能力,在验收过程中我们有了更多的构思空间,之前一直因为源代码梳理总结和技术验收成本过大,所导致的redis.conf配置加载逻辑一直没有实现。 +得益于 AI 的推理和重构能力,在验收过程中我们有了更多的构思空间。之前一直因为源代码梳理总结和技术验收成本过大,导致 redis.conf 配置加载逻辑一直没有实现。 因为笔者需要将慢查询时间设置为0,方便对慢查询指令做最后的验收工作,所以笔者索性再次对其提出加载配置的需求: @@ -448,70 +450,50 @@ slowlog-log-slower-than 0 ![slowlog get多条记录](https://oss.javaguide.cn/github/javaguide/ai/coding/m2.7/slowlog-get-multiple-records.png) -## MiniMax M2.7核心优势分析 +## 实战总结:AI 辅助编程的工作流思考 -通过对两个典型场景的深度测评,结合官方公布的基准测试数据,我们总结出MiniMax M2.7在开发辅助领域的核心优势: +通过两个典型场景的实战,总结一下使用 Trae + 大模型辅助编程的一些经验和思考。 -**基准测试表现**: +### AI 辅助编程能做什么 -![](images/benchmark-test-results.png) +在上述两个场景中,AI 辅助编程展现出了几个核心能力: -_数据来源:MiniMax官方发布及第三方评测机构_ +| 能力维度 | 场景表现 | 说明 | +| -------------- | ---------------------------------------- | ---------------------------------------- | +| 故障诊断与止血 | 场景一:快速定位连接池问题,提供降级方案 | 推理链条完整,能从异常栈帧梳理到调用链路 | +| 代码上下文理解 | 场景一:结合数据库 Schema 分析查询瓶颈 | 不局限于单文件,能关联跨模块的依赖关系 | +| 跨语言代码迁移 | 场景二:C 到 Go 的慢查询复刻 | 核心逻辑准确,工程规范适配有优化空间 | +| 复杂系统理解 | 场景二:Redis 源码分析 | 能把握设计意图,输出结构化技术文档 | -### 1. 强大的上下文理解能力 +### 实战中的经验与踩坑 -M2.7能够理解整个项目的代码结构和业务逻辑,而非孤立地处理单个问题点。在场景1中,它准确梳理了从接口请求到Redis操作的完整调用链路;在场景2中,它快速把握了Redis源代码的设计理念。 +**做得好的地方**: -### 2. 多层级问题处理能力 +- **快速收敛问题范围**:场景一中,模型从 N 处 Redis 调用快速定位到 4 种可能根因,再到最终确认 scan 操作导致连接池夯死,整个推理链条清晰 +- **多层级方案输出**:止血方案、根因分析、长期优化建议分层给出,符合实际排障流程 +- **TDD 自循环修复**:场景二中,指定 TDD 模式后,模型能根据测试反馈自我修复,减少人工干预 -| 问题层级 | M2.7表现 | -| -------- | -------------------------------- | -| 止血处理 | 提供快速应急方案,支持服务降级 | -| 根因定位 | 深入分析代码逻辑,识别架构问题 | -| 长期优化 | 给出数据结构和架构层面的改进建议 | +**需要注意的地方**: -### 3. 跨语言迁移能力 - -在场景2中,M2.7成功完成了从Redis C语言实现到Go语言复刻的技术文档编写,证明其在异构语言场景下的迁移和推理能力。 - -### 4. 开发效率提升 - -| 传统方式 | 使用M2.7 | 效率提升 | -| ------------ | -------------------- | ------------ | -| 3个工作日 | 数小时完成核心功能 | 约80% | -| 需要反复调试 | 自动修复和自循环验证 | 减少试错成本 | -| 依赖个人经验 | 结合最佳实践给出方案 | 降低经验门槛 | - -## 总结与建议 - -基于两个真实场景的试用体验,对MiniMax M2.7形成以下客观评价: - -### 能力验证总结 - -| 能力维度 | 场景表现 | 评价 | -| -------------- | --------------------------------------- | ------------------------------------ | -| 故障诊断与止血 | 场景1:快速定位连接池问题,提供降级方案 | 表现优秀,推理链条完整 | -| 跨语言代码迁移 | 场景2:C到Go的慢查询复刻 | 核心逻辑准确,工程规范适配有优化空间 | -| 复杂系统理解 | 场景2:Redis源码分析 | 设计意图把握到位 | -| 端到端交付 | 设计→编码→测试全流程 | 可独立完成,关键节点需人工确认 | +- **方案激进**:模型给出的某些方案(如清除 Redis 缓存)可能过于激进,生产环境需要更保守的策略,这一点必须人工把关 +- **工程规范适配**:生成的代码结构虽合理,但与个人/团队既有规范的契合度需要磨合。比如场景二中 `slowlog` 指令的文件组织就需要手动调整 +- **边界情况处理**:部分极端场景的防御性代码建议人工补充——AI 能帮你走到 90%,剩下的 10% 还得靠自己 +- **长流程一致性**:在复杂项目的持续迭代中,需要关注上下文记忆的衰减问题 -### 使用建议 +### 使用 Trae + 大模型的一些建议 -1. **适用场景**:线上故障应急、遗留系统重构、技术方案预研 -2. **最佳实践**: - - 提供完整上下文,明确约束条件 - - 复杂架构分阶段确认,避免一次性生成过多代码 - - 工程规范相关的文件组织需提前说明或后期调整 -3. **质量把控**:核心逻辑务必人工复核,特别是与既有代码风格的兼容性 +1. **提供完整上下文**:明确约束条件、编码规范、项目结构,模型输出质量会好很多 +2. **分阶段确认**:复杂架构不要一次性让 AI 生成过多代码,分阶段确认和调整更可控 +3. **关键决策人工把控**:架构层面的选择(如缓存策略、降级方案)需要开发者根据业务场景判断,AI 无法替你做 +4. **善用 TDD 模式**:指定测试驱动开发流程,让模型在测试反馈中自我修复,效率更高 -### 客观评价 +## 写在最后 -M2.7在代码理解和方案设计层面表现亮眼,能够显著缩短从问题到方案的时间。但在实际使用中也有一些需要注意的地方: +Trae 作为 AI 编程 IDE,在接入大模型后的体验是流畅的——Agent 模式下的上下文理解、任务拆解、代码生成、测试验收形成了完整的工作流。 -- **工程规范适配**:生成的代码结构虽合理,但与个人/团队既有规范的契合度需要磨合 -- **长流程一致性**:在复杂项目的持续迭代中,需要关注上下文记忆的衰减问题 -- **边界情况处理**:部分极端场景的防御性代码建议人工补充 +但工具终究只是工具。回顾本文的两个场景: -值得一提的是,M2.7 是国内第一个通过构建复杂 Agent Harness 以实现自我进化的模型。这套机制让模型能够在实际任务中不断优化自身的推理和代码生成能力,也是它在 SWE-Pro 等基准测试中取得不错成绩的技术基础之一。 +- **场景一的 Redis 故障排查**,需要对 Redis 连接池机制、scan 命令的时间复杂度有清晰认知,才能判断模型给出的分析是否合理。 +- **场景二的跨语言重构**,需要对 Redis 源码的设计理念、Go 语言的工程规范有深入理解,才能评估重构方案的质量。 -总体而言,M2.7已具备作为日常开发助手的实用价值,适合承担70%-80%的方案设计和编码工作,剩余部分仍需开发者把控。 +AI 编程工具能显著缩短"从想法到代码"的时间,但对底层原理的掌握、对系统架构的判断力,依然需要开发者自身去积累。用好 AI 的前提,是比 AI 更懂你在做什么。 From b2d47599ab4ea2bb8bbbf966d05b619638f9ecc0 Mon Sep 17 00:00:00 2001 From: Senrian <47714364+Senrian@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:15:19 +0800 Subject: [PATCH 5/7] test --- .../spring/spring-common-annotations.md | 1029 +---------------- 1 file changed, 1 insertion(+), 1028 deletions(-) diff --git a/docs/system-design/framework/spring/spring-common-annotations.md b/docs/system-design/framework/spring/spring-common-annotations.md index 3a2b006c8ea..30d74d25844 100644 --- a/docs/system-design/framework/spring/spring-common-annotations.md +++ b/docs/system-design/framework/spring/spring-common-annotations.md @@ -1,1028 +1 @@ ---- -title: Spring&SpringBoot常用注解总结 -description: Spring和SpringBoot常用注解大全,涵盖@Autowired、@Component、@RequestMapping等核心注解的用法详解。 -category: 框架 -tag: - - SpringBoot - - Spring -head: - - - meta - - name: keywords - content: Spring注解,Spring Boot注解,@SpringBootApplication,@Autowired,@RequestMapping,@Configuration,@Component,常用注解 ---- - -可以毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景。对于每一个注解本文都提供了具体用法,掌握这些内容后,使用 Spring Boot 来开发项目基本没啥大问题了! - -**为什么要写这篇文章?** - -最近看到网上有一篇关于 Spring Boot 常用注解的文章被广泛转载,但文章内容存在一些误导性,可能对没有太多实际使用经验的开发者不太友好。于是我花了几天时间总结了这篇文章,希望能够帮助大家更好地理解和使用 Spring 注解。 - -**因为个人能力和精力有限,如果有任何错误或遗漏,欢迎指正!非常感激!** - -## Spring Boot 基础注解 - -`@SpringBootApplication` 是 Spring Boot 应用的核心注解,通常用于标注主启动类。 - -示例: - -```java -@SpringBootApplication -public class SpringSecurityJwtGuideApplication { - public static void main(java.lang.String[] args) { - SpringApplication.run(SpringSecurityJwtGuideApplication.class, args); - } -} -``` - -我们可以把 `@SpringBootApplication`看作是下面三个注解的组合: - -- **`@EnableAutoConfiguration`**:启用 Spring Boot 的自动配置机制。 -- **`@ComponentScan`**:扫描 `@Component`、`@Service`、`@Repository`、`@Controller` 等注解的类。 -- **`@Configuration`**:允许注册额外的 Spring Bean 或导入其他配置类。 - -源码如下: - -```java -package org.springframework.boot.autoconfigure; -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@SpringBootConfiguration -@EnableAutoConfiguration -@ComponentScan(excludeFilters = { - @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), - @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) -public @interface SpringBootApplication { - ...... -} - -package org.springframework.boot; -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Configuration -public @interface SpringBootConfiguration { - -} -``` - -## Spring Bean - -### 依赖注入(Dependency Injection, DI) - -`@Autowired` 用于自动注入依赖项(即其他 Spring Bean)。它可以标注在构造器、字段、Setter 方法或配置方法上,Spring 容器会自动查找匹配类型的 Bean 并将其注入。 - -```java -@Service -public class UserServiceImpl implements UserService { - // ... -} - -@RestController -public class UserController { - // 字段注入 - @Autowired - private UserService userService; - // ... -} -``` - -当存在多个相同类型的 Bean 时,`@Autowired` 默认按类型注入可能产生歧义。此时,可以与 `@Qualifier` 结合使用,通过指定 Bean 的名称来精确选择需要注入的实例。 - -```java -@Repository("userRepositoryA") -public class UserRepositoryA implements UserRepository { /* ... */ } - -@Repository("userRepositoryB") -public class UserRepositoryB implements UserRepository { /* ... */ } - -@Service -public class UserService { - @Autowired - @Qualifier("userRepositoryA") // 指定注入名为 "userRepositoryA" 的 Bean - private UserRepository userRepository; - // ... -} -``` - -`@Primary`同样是为了解决同一类型存在多个 Bean 实例的注入问题。在 Bean 定义时(例如使用 `@Bean` 或类注解)添加 `@Primary` 注解,表示该 Bean 是**首选**的注入对象。当进行 `@Autowired` 注入时,如果没有使用 `@Qualifier` 指定名称,Spring 将优先选择带有 `@Primary` 的 Bean。 - -```java -@Primary // 将 UserRepositoryA 设为首选注入对象 -@Repository("userRepositoryA") -public class UserRepositoryA implements UserRepository { /* ... */ } - -@Repository("userRepositoryB") -public class UserRepositoryB implements UserRepository { /* ... */ } - -@Service -public class UserService { - @Autowired // 会自动注入 UserRepositoryA,因为它是 @Primary - private UserRepository userRepository; - // ... -} -``` - -`@Resource(name="beanName")`是 JSR-250 规范定义的注解,也用于依赖注入。它默认按**名称 (by Name)** 查找 Bean 进行注入,而 `@Autowired`默认按**类型 (by Type)** 。如果未指定 `name` 属性,它会尝试根据字段名或方法名查找,如果找不到,则回退到按类型查找(类似 `@Autowired`)。 - -`@Resource`只能标注在字段 和 Setter 方法上,不支持构造器注入。 - -```java -@Service -public class UserService { - @Resource(name = "userRepositoryA") - private UserRepository userRepository; - // ... -} -``` - -### Bean 作用域 - -`@Scope("scopeName")` 定义 Spring Bean 的作用域,即 Bean 实例的生命周期和可见范围。常用的作用域包括: - -- **singleton** : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。 -- **prototype** : 每次获取都会创建一个新的 bean 实例。也就是说,连续 `getBean()` 两次,得到的是不同的 Bean 实例。 -- **request** (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。 -- **session** (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。 -- **application/global-session** (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。 -- **websocket** (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。 - -```java -@Component -// 每次获取都会创建新的 PrototypeBean 实例 -@Scope("prototype") -public class PrototypeBean { - // ... -} -``` - -### Bean 注册 - -Spring 容器需要知道哪些类需要被管理为 Bean。除了使用 `@Bean` 方法显式声明(通常在 `@Configuration` 类中),更常见的方式是使用 Stereotype(构造型) 注解标记类,并配合组件扫描(Component Scanning)机制,让 Spring 自动发现并注册这些类作为 Bean。这些 Bean 后续可以通过 `@Autowired` 等方式注入到其他组件中。 - -下面是常见的一些注册 Bean 的注解: - -- `@Component`:通用的注解,可标注任意类为 `Spring` 组件。如果一个 Bean 不知道属于哪个层,可以使用`@Component` 注解标注。 -- `@Repository` : 对应持久层即 Dao 层,主要用于数据库相关操作。 -- `@Service` : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。 -- `@Controller` : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。 -- `@RestController`:一个组合注解,等效于 `@Controller` + `@ResponseBody`。它专门用于构建 RESTful Web 服务的控制器。标注了 `@RestController` 的类,其所有处理器方法(handler methods)的返回值都会被自动序列化(通常为 JSON)并写入 HTTP 响应体,而不是被解析为视图名称。 - -`@Controller` vs `@RestController`: - -- `@Controller`:主要用于传统的 Spring MVC 应用,方法返回值通常是逻辑视图名,需要视图解析器配合渲染页面。如果需要返回数据(如 JSON),则需要在方法上额外添加 `@ResponseBody` 注解。 -- `@RestController`:专为构建返回数据的 RESTful API 设计。类上使用此注解后,所有方法的返回值都会默认被视为响应体内容(相当于每个方法都隐式添加了 `@ResponseBody`),通常用于返回 JSON 或 XML 数据。在现代前后端分离的应用中,`@RestController` 是更常用的选择。 - -关于`@RestController` 和 `@Controller`的对比,请看这篇文章:[@RestController vs @Controller](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485544&idx=1&sn=3cc95b88979e28fe3bfe539eb421c6d8&chksm=cea247a3f9d5ceb5e324ff4b8697adc3e828ecf71a3468445e70221cce768d1e722085359907&token=1725092312&lang=zh_CN#rd)。 - -## 配置 - -### 声明配置类 - -`@Configuration` 主要用于声明一个类是 Spring 的配置类。虽然也可以用 `@Component` 注解替代,但 `@Configuration` 能够更明确地表达该类的用途(定义 Bean),语义更清晰,也便于 Spring 进行特定的处理(例如,通过 CGLIB 代理确保 `@Bean` 方法的单例行为)。 - -```java -@Configuration -public class AppConfig { - - // @Bean 注解用于在配置类中声明一个 Bean - @Bean - public TransferService transferService() { - return new TransferServiceImpl(); - } - - // 配置类中可以包含一个或多个 @Bean 方法。 -} -``` - -### 读取配置信息 - -在应用程序开发中,我们经常需要管理一些配置信息,例如数据库连接细节、第三方服务(如阿里云 OSS、短信服务、微信认证)的密钥或地址等。通常,这些信息会**集中存放在配置文件**(如 `application.yml` 或 `application.properties`)中,方便管理和修改。 - -Spring 提供了多种便捷的方式来读取这些配置信息。假设我们有如下 `application.yml` 文件: - -```yaml -wuhan2020: 2020年初武汉爆发了新型冠状病毒,疫情严重,但是,我相信一切都会过去!武汉加油!中国加油! - -my-profile: - name: Guide哥 - email: koushuangbwcx@163.com - -library: - location: 湖北武汉加油中国加油 - books: - - name: 天才基本法 - description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。 - - name: 时间的秩序 - description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。 - - name: 了不起的我 - description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻? -``` - -下面介绍几种常用的读取配置的方式: - -1、`@Value("${property.key}")` 注入配置文件(如 `application.properties` 或 `application.yml`)中的单个属性值。它还支持 Spring 表达式语言 (SpEL),可以实现更复杂的注入逻辑。 - -```java -@Value("${wuhan2020}") -String wuhan2020; -``` - -2、`@ConfigurationProperties`可以读取配置信息并与 Bean 绑定,用的更多一些。 - -```java -@Component -@ConfigurationProperties(prefix = "library") -class LibraryProperties { - @NotEmpty - private String location; - private List books; - - @Setter - @Getter - @ToString - static class Book { - String name; - String description; - } - 省略getter/setter - ...... -} -``` - -你可以像使用普通的 Spring Bean 一样,将其注入到类中使用。 - -```java -@Service -public class LibraryService { - - private final LibraryProperties libraryProperties; - - @Autowired - public LibraryService(LibraryProperties libraryProperties) { - this.libraryProperties = libraryProperties; - } - - public void printLibraryInfo() { - System.out.println(libraryProperties); - } -} -``` - -### 加载指定的配置文件 - -`@PropertySource` 注解允许加载自定义的配置文件。适用于需要将部分配置信息独立存储的场景。 - -```java -@Component -@PropertySource("classpath:website.properties") - -class WebSite { - @Value("${url}") - private String url; - - 省略getter/setter - ...... -} -``` - -**注意**:当使用 `@PropertySource` 时,确保外部文件路径正确,且文件在类路径(classpath)中。 - -更多内容请查看我的这篇文章:[10 分钟搞定 SpringBoot 如何优雅读取配置文件?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486181&idx=2&sn=10db0ae64ef501f96a5b0dbc4bd78786&chksm=cea2452ef9d5cc384678e456427328600971180a77e40c13936b19369672ca3e342c26e92b50&token=816772476&lang=zh_CN#rd) 。 - -## MVC - -### HTTP 请求 - -**5 种常见的请求类型:** - -- **GET**:请求从服务器获取特定资源。举个例子:`GET /users`(获取所有学生) -- **POST**:在服务器上创建一个新的资源。举个例子:`POST /users`(创建学生) -- **PUT**:更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:`PUT /users/12`(更新编号为 12 的学生) -- **DELETE**:从服务器删除特定的资源。举个例子:`DELETE /users/12`(删除编号为 12 的学生) -- **PATCH**:更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。 - -#### GET 请求 - -`@GetMapping("users")` 等价于`@RequestMapping(value="/users",method=RequestMethod.GET)`。 - -```java -@GetMapping("/users") -public ResponseEntity> getAllUsers() { - return userRepository.findAll(); -} -``` - -#### POST 请求 - -`@PostMapping("users")` 等价于`@RequestMapping(value="/users",method=RequestMethod.POST)`。 - -`@PostMapping` 通常与 `@RequestBody` 配合,用于接收 JSON 数据并映射为 Java 对象。 - -```java -@PostMapping("/users") -public ResponseEntity createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) { - return userRepository.save(userCreateRequest); -} -``` - -#### PUT 请求 - -`@PutMapping("/users/{userId}")` 等价于`@RequestMapping(value="/users/{userId}",method=RequestMethod.PUT)`。 - -```java -@PutMapping("/users/{userId}") -public ResponseEntity updateUser(@PathVariable(value = "userId") Long userId, - @Valid @RequestBody UserUpdateRequest userUpdateRequest) { - ...... -} -``` - -#### DELETE 请求 - -`@DeleteMapping("/users/{userId}")`等价于`@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)` - -```java -@DeleteMapping("/users/{userId}") -public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){ - ...... -} -``` - -#### PATCH 请求 - -一般实际项目中,我们都是 PUT 不够用了之后才用 PATCH 请求去更新数据。 - -```java - @PatchMapping("/profile") - public ResponseEntity updateStudent(@RequestBody StudentUpdateRequest studentUpdateRequest) { - studentRepository.updateDetail(studentUpdateRequest); - return ResponseEntity.ok().build(); - } -``` - -### 参数绑定 - -在处理 HTTP 请求时,Spring MVC 提供了多种注解用于绑定请求参数到方法参数中。以下是常见的参数绑定方式: - -#### 从 URL 路径中提取参数 - -`@PathVariable` 用于从 URL 路径中提取参数。例如: - -```java -@GetMapping("/klasses/{klassId}/teachers") -public List getTeachersByClass(@PathVariable("klassId") Long klassId) { - return teacherService.findTeachersByClass(klassId); -} -``` - -若请求 URL 为 `/klasses/123/teachers`,则 `klassId = 123`。 - -#### 绑定查询参数 - -`@RequestParam` 用于绑定查询参数。例如: - -```java -@GetMapping("/klasses/{klassId}/teachers") -public List getTeachersByClass(@PathVariable Long klassId, - @RequestParam(value = "type", required = false) String type) { - return teacherService.findTeachersByClassAndType(klassId, type); -} -``` - -若请求 URL 为 `/klasses/123/teachers?type=web`,则 `klassId = 123`,`type = web`。 - -#### 绑定请求体中的 JSON 数据 - -`@RequestBody` 用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且**Content-Type 为 application/json** 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用`HttpMessageConverter`或者自定义的`HttpMessageConverter`将请求的 body 中的 json 字符串转换为 java 对象。 - -我用一个简单的例子来给演示一下基本使用! - -我们有一个注册的接口: - -```java -@PostMapping("/sign-up") -public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) { - userService.save(userRegisterRequest); - return ResponseEntity.ok().build(); -} -``` - -`UserRegisterRequest`对象: - -```java -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserRegisterRequest { - @NotBlank - private String userName; - @NotBlank - private String password; - @NotBlank - private String fullName; -} -``` - -我们发送 post 请求到这个接口,并且 body 携带 JSON 数据: - -```json -{ "userName": "coder", "fullName": "shuangkou", "password": "123456" } -``` - -这样我们的后端就可以直接把 json 格式的数据映射到我们的 `UserRegisterRequest` 类上。 - -![](./images/spring-annotations/@RequestBody.png) - -**注意**: - -- 一个方法只能有一个 `@RequestBody` 参数,但可以有多个 `@PathVariable` 和 `@RequestParam`。 -- 如果需要接收多个复杂对象,建议合并成一个单一对象。 - -## 数据校验 - -数据校验是保障系统稳定性和安全性的关键环节。即使在用户界面(前端)已经实施了数据校验,**后端服务仍必须对接收到的数据进行再次校验**。这是因为前端校验可以被轻易绕过(例如,通过开发者工具修改请求或使用 Postman、curl 等 HTTP 工具直接调用 API),恶意或错误的数据可能直接发送到后端。因此,后端校验是防止非法数据、维护数据一致性、确保业务逻辑正确执行的最后一道,也是最重要的一道防线。 - -Bean Validation 是一套定义 JavaBean 参数校验标准的规范 (JSR 303, 349, 380),它提供了一系列注解,可以直接用于 JavaBean 的属性上,从而实现便捷的参数校验。 - -- **JSR 303 (Bean Validation 1.0):** 奠定了基础,引入了核心校验注解(如 `@NotNull`、`@Size`、`@Min`、`@Max` 等),定义了如何通过注解的方式对 JavaBean 的属性进行校验,并支持嵌套对象校验和自定义校验器。 -- **JSR 349 (Bean Validation 1.1):** 在 1.0 基础上进行扩展,例如引入了对方法参数和返回值校验的支持、增强了对分组校验(Group Validation)的处理。 -- **JSR 380 (Bean Validation 2.0):** 拥抱 Java 8 的新特性,并进行了一些改进,例如支持 `java.time` 包中的日期和时间类型、引入了一些新的校验注解(如 `@NotEmpty`, `@NotBlank`等)。 - -Bean Validation 本身只是一套**规范(接口和注解)**,我们需要一个实现了这套规范的**具体框架**来执行校验逻辑。目前,**Hibernate Validator** 是 Bean Validation 规范最权威、使用最广泛的参考实现。 - -- Hibernate Validator 4.x 实现了 Bean Validation 1.0 (JSR 303)。 -- Hibernate Validator 5.x 实现了 Bean Validation 1.1 (JSR 349)。 -- Hibernate Validator 6.x 及更高版本实现了 Bean Validation 2.0 (JSR 380)。 - -在 Spring Boot 项目中使用 Bean Validation 非常方便,这得益于 Spring Boot 的自动配置能力。关于依赖引入,需要注意: - -- 在较早版本的 Spring Boot(通常指 2.3.x 之前)中,`spring-boot-starter-web` 依赖默认包含了 hibernate-validator。因此,只要引入了 Web Starter,就无需额外添加校验相关的依赖。 -- 从 Spring Boot 2.3.x 版本开始,为了更精细化的依赖管理,校验相关的依赖被移出了 spring-boot-starter-web。如果你的项目使用了这些或更新的版本,并且需要 Bean Validation 功能,那么你需要显式地添加 `spring-boot-starter-validation` 依赖: - -```xml - - org.springframework.boot - spring-boot-starter-validation - -``` - -![](https://oss.javaguide.cn/2021/03/c7bacd12-1c1a-4e41-aaaf-4cad840fc073.png) - -非 SpringBoot 项目需要自行引入相关依赖包,这里不多做讲解,具体可以查看我的这篇文章:[如何在 Spring/Spring Boot 中做参数校验?你需要了解的都在这里!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485783&idx=1&sn=a407f3b75efa17c643407daa7fb2acd6&chksm=cea2469cf9d5cf8afbcd0a8a1c9cc4294d6805b8e01bee6f76bb2884c5bc15478e91459def49&token=292197051&lang=zh_CN#rd)。 - -👉 需要注意的是:所有的注解,推荐使用 JSR 注解,即`javax.validation.constraints`,而不是`org.hibernate.validator.constraints` - -### 一些常用的字段验证的注解 - -Bean Validation 规范及其实现(如 Hibernate Validator)提供了丰富的注解,用于声明式地定义校验规则。以下是一些常用的注解及其说明: - -- `@NotNull`: 检查被注解的元素(任意类型)不能为 `null`。 -- `@NotEmpty`: 检查被注解的元素(如 `CharSequence`、`Collection`、`Map`、`Array`)不能为 `null` 且其大小/长度不能为 0。注意:对于字符串,`@NotEmpty` 允许包含空白字符的字符串,如 `" "`。 -- `@NotBlank`: 检查被注解的 `CharSequence`(如 `String`)不能为 `null`,并且去除首尾空格后的长度必须大于 0。(即,不能为空白字符串)。 -- `@Null`: 检查被注解的元素必须为 `null`。 -- `@AssertTrue` / `@AssertFalse`: 检查被注解的 `boolean` 或 `Boolean` 类型元素必须为 `true` / `false`。 -- `@Min(value)` / `@Max(value)`: 检查被注解的数字类型(或其字符串表示)的值必须大于等于 / 小于等于指定的 `value`。适用于整数类型(`byte`、`short`、`int`、`long`、`BigInteger` 等)。 -- `@DecimalMin(value)` / `@DecimalMax(value)`: 功能类似 `@Min` / `@Max`,但适用于包含小数的数字类型(`BigDecimal`、`BigInteger`、`CharSequence`、`byte`、`short`、`int`、`long`及其包装类)。 `value` 必须是数字的字符串表示。 -- `@Size(min=, max=)`: 检查被注解的元素(如 `CharSequence`、`Collection`、`Map`、`Array`)的大小/长度必须在指定的 `min` 和 `max` 范围之内(包含边界)。 -- `@Digits(integer=, fraction=)`: 检查被注解的数字类型(或其字符串表示)的值,其整数部分的位数必须 ≤ `integer`,小数部分的位数必须 ≤ `fraction`。 -- `@Pattern(regexp=, flags=)`: 检查被注解的 `CharSequence`(如 `String`)是否匹配指定的正则表达式 (`regexp`)。`flags` 可以指定匹配模式(如不区分大小写)。 -- `@Email`: 检查被注解的 `CharSequence`(如 `String`)是否符合 Email 格式(内置了一个相对宽松的正则表达式)。 -- `@Past` / `@Future`: 检查被注解的日期或时间类型(`java.util.Date`、`java.util.Calendar`、JSR 310 `java.time` 包下的类型)是否在当前时间之前 / 之后。 -- `@PastOrPresent` / `@FutureOrPresent`: 类似 `@Past` / `@Future`,但允许等于当前时间。 -- …… - -### 验证请求体(RequestBody) - -当 Controller 方法使用 `@RequestBody` 注解来接收请求体并将其绑定到一个对象时,可以在该参数前添加 `@Valid` 注解来触发对该对象的校验。如果验证失败,它将抛出`MethodArgumentNotValidException`。 - -```java -@Data -@AllArgsConstructor -@NoArgsConstructor -public class Person { - @NotNull(message = "classId 不能为空") - private String classId; - - @Size(max = 33) - @NotNull(message = "name 不能为空") - private String name; - - @Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围") - @NotNull(message = "sex 不能为空") - private String sex; - - @Email(message = "email 格式不正确") - @NotNull(message = "email 不能为空") - private String email; -} - - -@RestController -@RequestMapping("/api") -public class PersonController { - @PostMapping("/person") - public ResponseEntity getPerson(@RequestBody @Valid Person person) { - return ResponseEntity.ok().body(person); - } -} -``` - -### 验证请求参数(Path Variables 和 Request Parameters) - -对于直接映射到方法参数的简单类型数据(如路径变量 `@PathVariable` 或请求参数 `@RequestParam`),校验方式略有不同: - -1. **在 Controller 类上添加 `@Validated` 注解**:这个注解是 Spring 提供的(非 JSR 标准),它使得 Spring 能够处理方法级别的参数校验注解。**这是必需步骤。** -2. **将校验注解直接放在方法参数上**:将 `@Min`, `@Max`, `@Size`, `@Pattern` 等校验注解直接应用于对应的 `@PathVariable` 或 `@RequestParam` 参数。 - -一定一定不要忘记在类上加上 `@Validated` 注解了,这个参数可以告诉 Spring 去校验方法参数。 - -```java -@RestController -@RequestMapping("/api") -@Validated // 关键步骤 1: 必须在类上添加 @Validated -public class PersonController { - - @GetMapping("/person/{id}") - public ResponseEntity getPersonByID( - @PathVariable("id") - @Max(value = 5, message = "ID 不能超过 5") // 关键步骤 2: 校验注解直接放在参数上 - Integer id - ) { - // 如果传入的 id > 5,Spring 会在进入方法体前抛出 ConstraintViolationException 异常。 - // 全局异常处理器同样需要处理此异常。 - return ResponseEntity.ok().body(id); - } - - @GetMapping("/person") - public ResponseEntity findPersonByName( - @RequestParam("name") - @NotBlank(message = "姓名不能为空") // 同样适用于 @RequestParam - @Size(max = 10, message = "姓名长度不能超过 10") - String name - ) { - return ResponseEntity.ok().body("Found person: " + name); - } -} -``` - -## 全局异常处理 - -介绍一下我们 Spring 项目必备的全局处理 Controller 层异常。 - -**相关注解:** - -1. `@ControllerAdvice` :注解定义全局异常处理类 -2. `@ExceptionHandler` :注解声明异常处理方法 - -如何使用呢?拿我们在第 5 节参数校验这块来举例子。如果方法参数不对的话就会抛出`MethodArgumentNotValidException`,我们来处理这个异常。 - -```java -@ControllerAdvice -@ResponseBody -public class GlobalExceptionHandler { - - /** - * 请求参数异常处理 - */ - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) { - ...... - } -} -``` - -更多关于 Spring Boot 异常处理的内容,请看我的这两篇文章: - -1. [SpringBoot 处理异常的几种常见姿势](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485568&idx=2&sn=c5ba880fd0c5d82e39531fa42cb036ac&chksm=cea2474bf9d5ce5dcbc6a5f6580198fdce4bc92ef577579183a729cb5d1430e4994720d59b34&token=2133161636&lang=zh_CN#rd) -2. [使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486379&idx=2&sn=48c29ae65b3ed874749f0803f0e4d90e&chksm=cea24460f9d5cd769ed53ad7e17c97a7963a89f5350e370be633db0ae8d783c3a3dbd58c70f8&token=1054498516&lang=zh_CN#rd) - -## 事务 - -在要开启事务的方法上使用`@Transactional`注解即可! - -```java -@Transactional(rollbackFor = Exception.class) -public void save() { - ...... -} - -``` - -我们知道 Exception 分为运行时异常 RuntimeException 和非运行时异常。在`@Transactional`注解中如果不配置`rollbackFor`属性,那么事务只会在遇到`RuntimeException`的时候才会回滚,加上`rollbackFor=Exception.class`,可以让事务在遇到非运行时异常时也回滚。 - -`@Transactional` 注解一般可以作用在`类`或者`方法`上。 - -- **作用于类**:当把`@Transactional` 注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息。 -- **作用于方法**:当类配置了`@Transactional`,方法也配置了`@Transactional`,方法的事务会覆盖类的事务配置信息。 - -更多关于 Spring 事务的内容请查看我的这篇文章:[可能是最漂亮的 Spring 事务管理详解](./spring-transaction.md) 。 - -## JPA - -Spring Data JPA 提供了一系列注解和功能,帮助开发者轻松实现 ORM(对象关系映射)。 - -### 创建表 - -`@Entity` 用于声明一个类为 JPA 实体类,与数据库中的表映射。`@Table` 指定实体对应的表名。 - -```java -@Entity -@Table(name = "role") -public class Role { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; - private String description; - - // 省略 getter/setter -} -``` - -### 主键生成策略 - -`@Id`声明字段为主键。`@GeneratedValue` 指定主键的生成策略。 - -JPA 提供了 4 种主键生成策略: - -- **`GenerationType.TABLE`**:通过数据库表生成主键。 -- **`GenerationType.SEQUENCE`**:通过数据库序列生成主键(适用于 Oracle 等数据库)。 -- **`GenerationType.IDENTITY`**:主键自增长(适用于 MySQL 等数据库)。 -- **`GenerationType.AUTO`**:由 JPA 自动选择合适的生成策略(默认策略)。 - -```java -@Id -@GeneratedValue(strategy = GenerationType.IDENTITY) -private Long id; -``` - -通过 `@GenericGenerator` 声明自定义主键生成策略: - -```java -@Id -@GeneratedValue(generator = "IdentityIdGenerator") -@GenericGenerator(name = "IdentityIdGenerator", strategy = "identity") -private Long id; -``` - -等价于: - -```java -@Id -@GeneratedValue(strategy = GenerationType.IDENTITY) -private Long id; -``` - -JPA 提供的主键生成策略有如下几种: - -```java -public class DefaultIdentifierGeneratorFactory - implements MutableIdentifierGeneratorFactory, Serializable, ServiceRegistryAwareService { - - @SuppressWarnings("deprecation") - public DefaultIdentifierGeneratorFactory() { - register( "uuid2", UUIDGenerator.class ); - register( "guid", GUIDGenerator.class ); // can be done with UUIDGenerator + strategy - register( "uuid", UUIDHexGenerator.class ); // "deprecated" for new use - register( "uuid.hex", UUIDHexGenerator.class ); // uuid.hex is deprecated - register( "assigned", Assigned.class ); - register( "identity", IdentityGenerator.class ); - register( "select", SelectGenerator.class ); - register( "sequence", SequenceStyleGenerator.class ); - register( "seqhilo", SequenceHiLoGenerator.class ); - register( "increment", IncrementGenerator.class ); - register( "foreign", ForeignGenerator.class ); - register( "sequence-identity", SequenceIdentityGenerator.class ); - register( "enhanced-sequence", SequenceStyleGenerator.class ); - register( "enhanced-table", TableGenerator.class ); - } - - public void register(String strategy, Class generatorClass) { - LOG.debugf( "Registering IdentifierGenerator strategy [%s] -> [%s]", strategy, generatorClass.getName() ); - final Class previous = generatorStrategyToClassNameMap.put( strategy, generatorClass ); - if ( previous != null ) { - LOG.debugf( " - overriding [%s]", previous.getName() ); - } - } - -} -``` - -### 字段映射 - -`@Column` 用于指定实体字段与数据库列的映射关系。 - -- **`name`**:指定数据库列名。 -- **`nullable`**:指定是否允许为 `null`。 -- **`length`**:设置字段的长度(仅适用于 `String` 类型)。 -- **`columnDefinition`**:指定字段的数据库类型和默认值。 - -```java -@Column(name = "user_name", nullable = false, length = 32) -private String userName; - -@Column(columnDefinition = "tinyint(1) default 1") -private Boolean enabled; -``` - -### 忽略字段 - -`@Transient` 用于声明不需要持久化的字段。 - -```java -@Entity -public class User { - - @Transient - private String temporaryField; // 不会映射到数据库表中 -} -``` - -其他不被持久化的字段方式: - -- **`static`**:静态字段不会被持久化。 -- **`final`**:最终字段不会被持久化。 -- **`transient`**:使用 Java 的 `transient` 关键字声明的字段不会被序列化或持久化。 - -### 大字段存储 - -`@Lob` 用于声明大字段(如 `CLOB` 或 `BLOB`)。 - -```java -@Lob -@Column(name = "content", columnDefinition = "LONGTEXT NOT NULL") -private String content; -``` - -### 枚举类型映射 - -`@Enumerated` 用于将枚举类型映射为数据库字段。 - -- **`EnumType.ORDINAL`**:存储枚举的序号(默认)。 -- **`EnumType.STRING`**:存储枚举的名称(推荐)。 - -```java -public enum Gender { - MALE, - FEMALE -} - -@Entity -public class User { - - @Enumerated(EnumType.STRING) - private Gender gender; -} -``` - -数据库中存储的值为 `MALE` 或 `FEMALE`。 - -### 审计功能 - -通过 JPA 的审计功能,可以在实体中自动记录创建时间、更新时间、创建人和更新人等信息。 - -审计基类: - -```java -@Data -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -public abstract class AbstractAuditBase { - - @CreatedDate - @Column(updatable = false) - private Instant createdAt; - - @LastModifiedDate - private Instant updatedAt; - - @CreatedBy - @Column(updatable = false) - private String createdBy; - - @LastModifiedBy - private String updatedBy; -} -``` - -配置审计功能: - -```java -@Configuration -@EnableJpaAuditing -public class AuditConfig { - - @Bean - public AuditorAware auditorProvider() { - return () -> Optional.ofNullable(SecurityContextHolder.getContext()) - .map(SecurityContext::getAuthentication) - .filter(Authentication::isAuthenticated) - .map(Authentication::getName); - } -} -``` - -简单介绍一下上面涉及到的一些注解: - -1. `@CreatedDate`: 表示该字段为创建时间字段,在这个实体被 insert 的时候,会设置值 -2. `@CreatedBy` :表示该字段为创建人,在这个实体被 insert 的时候,会设置值 `@LastModifiedDate`、`@LastModifiedBy`同理。 -3. `@EnableJpaAuditing`:开启 JPA 审计功能。 - -### 修改和删除操作 - -`@Modifying` 注解用于标识修改或删除操作,必须与 `@Transactional` 一起使用。 - -```java -@Repository -public interface UserRepository extends JpaRepository { - - @Modifying - @Transactional - void deleteByUserName(String userName); -} -``` - -### 关联关系 - -JPA 提供了 4 种关联关系的注解: - -- **`@OneToOne`**:一对一关系。 -- **`@OneToMany`**:一对多关系。 -- **`@ManyToOne`**:多对一关系。 -- **`@ManyToMany`**:多对多关系。 - -```java -@Entity -public class User { - - @OneToOne - private Profile profile; - - @OneToMany(mappedBy = "user") - private List orders; -} -``` - -## JSON 数据处理 - -在 Web 开发中,经常需要处理 Java 对象与 JSON 格式之间的转换。Spring 通常集成 Jackson 库来完成此任务,以下是一些常用的 Jackson 注解,可以帮助我们定制化 JSON 的序列化(Java 对象转 JSON)和反序列化(JSON 转 Java 对象)过程。 - -### 过滤 JSON 字段 - -有时我们不希望 Java 对象的某些字段被包含在最终生成的 JSON 中,或者在将 JSON 转换为 Java 对象时不处理某些 JSON 属性。 - -`@JsonIgnoreProperties` 作用在类上用于过滤掉特定字段不返回或者不解析。 - -```java -// 在生成 JSON 时忽略 userRoles 属性 -// 如果允许未知属性(即 JSON 中有而类中没有的属性),可以添加 ignoreUnknown = true -@JsonIgnoreProperties({"userRoles"}) -public class User { - private String userName; - private String fullName; - private String password; - private List userRoles = new ArrayList<>(); - // getters and setters... -} -``` - -`@JsonIgnore`作用于字段或`getter/setter` 方法级别,用于指定在序列化或反序列化时忽略该特定属性。 - -```java -public class User { - private String userName; - private String fullName; - private String password; - - // 在生成 JSON 时忽略 userRoles 属性 - @JsonIgnore - private List userRoles = new ArrayList<>(); - // getters and setters... -} -``` - -`@JsonIgnoreProperties` 更适用于在类定义时明确排除多个字段,或继承场景下的字段排除;`@JsonIgnore` 则更直接地用于标记单个具体字段。 - -### 格式化 JSON 数据 - -`@JsonFormat` 用于指定属性在序列化和反序列化时的格式。常用于日期时间类型的格式化。 - -比如: - -```java -// 指定 Date 类型序列化为 ISO 8601 格式字符串,并设置时区为 GMT -@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "GMT") -private Date date; -``` - -### 扁平化 JSON 对象 - -`@JsonUnwrapped` 注解作用于字段上,用于在序列化时将其嵌套对象的属性“提升”到当前对象的层级,反序列化时执行相反操作。这可以使 JSON 结构更扁平。 - -假设有 `Account` 类,包含 `Location` 和 `PersonInfo` 两个嵌套对象。 - -```java -@Getter -@Setter -@ToString -public class Account { - private Location location; - private PersonInfo personInfo; - - @Getter - @Setter - @ToString - public static class Location { - private String provinceName; - private String countyName; - } - @Getter - @Setter - @ToString - public static class PersonInfo { - private String userName; - private String fullName; - } -} - -``` - -未扁平化之前的 JSON 结构: - -```json -{ - "location": { - "provinceName": "湖北", - "countyName": "武汉" - }, - "personInfo": { - "userName": "coder1234", - "fullName": "shaungkou" - } -} -``` - -使用`@JsonUnwrapped` 扁平对象: - -```java -@Getter -@Setter -@ToString -public class Account { - @JsonUnwrapped - private Location location; - @JsonUnwrapped - private PersonInfo personInfo; - ...... -} -``` - -扁平化后的 JSON 结构: - -```json -{ - "provinceName": "湖北", - "countyName": "武汉", - "userName": "coder1234", - "fullName": "shaungkou" -} -``` - -## 测试 - -`@ActiveProfiles`一般作用于测试类上, 用于声明生效的 Spring 配置文件。 - -```java -// 指定在 RANDOM_PORT 上启动应用上下文,并激活 "test" profile -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles("test") -@Slf4j -public abstract class TestBase { - // Common test setup or abstract methods... -} -``` - -`@Test` 是 JUnit 框架(通常是 JUnit 5 Jupiter)提供的注解,用于标记一个方法为测试方法。虽然不是 Spring 自身的注解,但它是执行单元测试和集成测试的基础。 - -`@Transactional`被声明的测试方法的数据会回滚,避免污染测试数据。 - -`@WithMockUser` 是 Spring Security Test 模块提供的注解,用于在测试期间模拟一个已认证的用户。可以方便地指定用户名、密码、角色(authorities)等信息,从而测试受安全保护的端点或方法。 - -```java -public class MyServiceTest extends TestBase { // Assuming TestBase provides Spring context - - @Test - @Transactional // 测试数据将回滚 - @WithMockUser(username = "test-user", authorities = { "ROLE_TEACHER", "read" }) // 模拟一个名为 "test-user",拥有 TEACHER 角色和 read 权限的用户 - void should_perform_action_requiring_teacher_role() throws Exception { - // ... 测试逻辑 ... - // 这里可以调用需要 "ROLE_TEACHER" 权限的服务方法 - } -} -``` - - +test \ No newline at end of file From 3a44d67efea856c59e661b1bff2409a86680caad Mon Sep 17 00:00:00 2001 From: Senrian <47714364+Senrian@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:20:32 +0800 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3Spring=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3=E6=96=87=E7=AB=A0=E6=A0=87=E9=A2=98=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=B3=A8=E8=A7=A3=E5=88=86=E7=B1=BB=E6=80=BB=E7=BB=93?= =?UTF-8?q?=E8=A1=A8=20(fix=20#2656)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/spring-common-annotations.md | 1035 ++++++++++++++++- 1 file changed, 1034 insertions(+), 1 deletion(-) diff --git a/docs/system-design/framework/spring/spring-common-annotations.md b/docs/system-design/framework/spring/spring-common-annotations.md index 30d74d25844..5135603b5de 100644 --- a/docs/system-design/framework/spring/spring-common-annotations.md +++ b/docs/system-design/framework/spring/spring-common-annotations.md @@ -1 +1,1034 @@ -test \ No newline at end of file +--- +title: Spring&SpringMVC&SpringBoot常用注解总结 +description: Spring和SpringBoot常用注解大全,涵盖@Autowired、@Component、@RequestMapping等核心注解的用法详解。 +category: 框架 +tag: + - SpringBoot + - Spring +head: + - - meta + - name: keywords + content: Spring注解,Spring Boot注解,@SpringBootApplication,@Autowired,@RequestMapping,@Configuration,@Component,常用注解 +--- + +可以毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景。对于每一个注解本文都提供了具体用法,掌握这些内容后,使用 Spring Boot 来开发项目基本没啥大问题了! + +**为什么要写这篇文章?** + +最近看到网上有一篇关于 Spring Boot 常用注解的文章被广泛转载,但文章内容存在一些误导性,可能对没有太多实际使用经验的开发者不太友好。于是我花了几天时间总结了这篇文章,希望能够帮助大家更好地理解和使用 Spring 注解。 + +**因为个人能力和精力有限,如果有任何错误或遗漏,欢迎指正!非常感激!** + +## Spring Boot 基础注解 + +`@SpringBootApplication` 是 Spring Boot 应用的核心注解,通常用于标注主启动类。 + +示例: + +```java +@SpringBootApplication +public class SpringSecurityJwtGuideApplication { + public static void main(java.lang.String[] args) { + SpringApplication.run(SpringSecurityJwtGuideApplication.class, args); + } +} +``` + +我们可以把 `@SpringBootApplication`看作是下面三个注解的组合: + +- **`@EnableAutoConfiguration`**:启用 Spring Boot 的自动配置机制。 +- **`@ComponentScan`**:扫描 `@Component`、`@Service`、`@Repository`、`@Controller` 等注解的类。 +- **`@Configuration`**:允许注册额外的 Spring Bean 或导入其他配置类。 + +源码如下: + +```java +package org.springframework.boot.autoconfigure; +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@SpringBootConfiguration +@EnableAutoConfiguration +@ComponentScan(excludeFilters = { + @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), + @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) +public @interface SpringBootApplication { + ...... +} + +package org.springframework.boot; +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Configuration +public @interface SpringBootConfiguration { + +} +``` + +## Spring Bean + +### 依赖注入(Dependency Injection, DI) + +`@Autowired` 用于自动注入依赖项(即其他 Spring Bean)。它可以标注在构造器、字段、Setter 方法或配置方法上,Spring 容器会自动查找匹配类型的 Bean 并将其注入。 + +```java +@Service +public class UserServiceImpl implements UserService { + // ... +} + +@RestController +public class UserController { + // 字段注入 + @Autowired + private UserService userService; + // ... +} +``` + +当存在多个相同类型的 Bean 时,`@Autowired` 默认按类型注入可能产生歧义。此时,可以与 `@Qualifier` 结合使用,通过指定 Bean 的名称来精确选择需要注入的实例。 + +```java +@Repository("userRepositoryA") +public class UserRepositoryA implements UserRepository { /* ... */ } + +@Repository("userRepositoryB") +public class UserRepositoryB implements UserRepository { /* ... */ } + +@Service +public class UserService { + @Autowired + @Qualifier("userRepositoryA") // 指定注入名为 "userRepositoryA" 的 Bean + private UserRepository userRepository; + // ... +} +``` + +`@Primary`同样是为了解决同一类型存在多个 Bean 实例的注入问题。在 Bean 定义时(例如使用 `@Bean` 或类注解)添加 `@Primary` 注解,表示该 Bean 是**首选**的注入对象。当进行 `@Autowired` 注入时,如果没有使用 `@Qualifier` 指定名称,Spring 将优先选择带有 `@Primary` 的 Bean。 + +```java +@Primary // 将 UserRepositoryA 设为首选注入对象 +@Repository("userRepositoryA") +public class UserRepositoryA implements UserRepository { /* ... */ } + +@Repository("userRepositoryB") +public class UserRepositoryB implements UserRepository { /* ... */ } + +@Service +public class UserService { + @Autowired // 会自动注入 UserRepositoryA,因为它是 @Primary + private UserRepository userRepository; + // ... +} +``` + +`@Resource(name="beanName")`是 JSR-250 规范定义的注解,也用于依赖注入。它默认按**名称 (by Name)** 查找 Bean 进行注入,而 `@Autowired`默认按**类型 (by Type)** 。如果未指定 `name` 属性,它会尝试根据字段名或方法名查找,如果找不到,则回退到按类型查找(类似 `@Autowired`)。 + +`@Resource`只能标注在字段 和 Setter 方法上,不支持构造器注入。 + +```java +@Service +public class UserService { + @Resource(name = "userRepositoryA") + private UserRepository userRepository; + // ... +} +``` + +### Bean 作用域 + +`@Scope("scopeName")` 定义 Spring Bean 的作用域,即 Bean 实例的生命周期和可见范围。常用的作用域包括: + +- **singleton** : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。 +- **prototype** : 每次获取都会创建一个新的 bean 实例。也就是说,连续 `getBean()` 两次,得到的是不同的 Bean 实例。 +- **request** (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。 +- **session** (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。 +- **application/global-session** (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。 +- **websocket** (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。 + +```java +@Component +// 每次获取都会创建新的 PrototypeBean 实例 +@Scope("prototype") +public class PrototypeBean { + // ... +} +``` + +### Bean 注册 + +Spring 容器需要知道哪些类需要被管理为 Bean。除了使用 `@Bean` 方法显式声明(通常在 `@Configuration` 类中),更常见的方式是使用 Stereotype(构造型) 注解标记类,并配合组件扫描(Component Scanning)机制,让 Spring 自动发现并注册这些类作为 Bean。这些 Bean 后续可以通过 `@Autowired` 等方式注入到其他组件中。 + +下面是常见的一些注册 Bean 的注解: + +- `@Component`:通用的注解,可标注任意类为 `Spring` 组件。如果一个 Bean 不知道属于哪个层,可以使用`@Component` 注解标注。 +- `@Repository` : 对应持久层即 Dao 层,主要用于数据库相关操作。 +- `@Service` : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。 +- `@Controller` : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。 +- `@RestController`:一个组合注解,等效于 `@Controller` + `@ResponseBody`。它专门用于构建 RESTful Web 服务的控制器。标注了 `@RestController` 的类,其所有处理器方法(handler methods)的返回值都会被自动序列化(通常为 JSON)并写入 HTTP 响应体,而不是被解析为视图名称。 + +`@Controller` vs `@RestController`: + +- `@Controller`:主要用于传统的 Spring MVC 应用,方法返回值通常是逻辑视图名,需要视图解析器配合渲染页面。如果需要返回数据(如 JSON),则需要在方法上额外添加 `@ResponseBody` 注解。 +- `@RestController`:专为构建返回数据的 RESTful API 设计。类上使用此注解后,所有方法的返回值都会默认被视为响应体内容(相当于每个方法都隐式添加了 `@ResponseBody`),通常用于返回 JSON 或 XML 数据。在现代前后端分离的应用中,`@RestController` 是更常用的选择。 + +关于`@RestController` 和 `@Controller`的对比,请看这篇文章:[@RestController vs @Controller](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485544&idx=1&sn=3cc95b88979e28fe3bfe539eb421c6d8&chksm=cea247a3f9d5ceb5e324ff4b8697adc3e828ecf71a3468445e70221cce768d1e722085359907&token=1725092312&lang=zh_CN#rd)。 + +## 配置 + +### 声明配置类 + +`@Configuration` 主要用于声明一个类是 Spring 的配置类。虽然也可以用 `@Component` 注解替代,但 `@Configuration` 能够更明确地表达该类的用途(定义 Bean),语义更清晰,也便于 Spring 进行特定的处理(例如,通过 CGLIB 代理确保 `@Bean` 方法的单例行为)。 + +```java +@Configuration +public class AppConfig { + + // @Bean 注解用于在配置类中声明一个 Bean + @Bean + public TransferService transferService() { + return new TransferServiceImpl(); + } + + // 配置类中可以包含一个或多个 @Bean 方法。 +} +``` + +### 读取配置信息 + +在应用程序开发中,我们经常需要管理一些配置信息,例如数据库连接细节、第三方服务(如阿里云 OSS、短信服务、微信认证)的密钥或地址等。通常,这些信息会**集中存放在配置文件**(如 `application.yml` 或 `application.properties`)中,方便管理和修改。 + +Spring 提供了多种便捷的方式来读取这些配置信息。假设我们有如下 `application.yml` 文件: + +```yaml +wuhan2020: 2020年初武汉爆发了新型冠状病毒,疫情严重,但是,我相信一切都会过去!武汉加油!中国加油! + +my-profile: + name: Guide哥 + email: koushuangbwcx@163.com + +library: + location: 湖北武汉加油中国加油 + books: + - name: 天才基本法 + description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。 + - name: 时间的秩序 + description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。 + - name: 了不起的我 + description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻? +``` + +下面介绍几种常用的读取配置的方式: + +1、`@Value("${property.key}")` 注入配置文件(如 `application.properties` 或 `application.yml`)中的单个属性值。它还支持 Spring 表达式语言 (SpEL),可以实现更复杂的注入逻辑。 + +```java +@Value("${wuhan2020}") +String wuhan2020; +``` + +2、`@ConfigurationProperties`可以读取配置信息并与 Bean 绑定,用的更多一些。 + +```java +@Component +@ConfigurationProperties(prefix = "library") +class LibraryProperties { + @NotEmpty + private String location; + private List books; + + @Setter + @Getter + @ToString + static class Book { + String name; + String description; + } + 省略getter/setter + ...... +} +``` + +你可以像使用普通的 Spring Bean 一样,将其注入到类中使用。 + +```java +@Service +public class LibraryService { + + private final LibraryProperties libraryProperties; + + @Autowired + public LibraryService(LibraryProperties libraryProperties) { + this.libraryProperties = libraryProperties; + } + + public void printLibraryInfo() { + System.out.println(libraryProperties); + } +} +``` + +### 加载指定的配置文件 + +`@PropertySource` 注解允许加载自定义的配置文件。适用于需要将部分配置信息独立存储的场景。 + +```java +@Component +@PropertySource("classpath:website.properties") + +class WebSite { + @Value("${url}") + private String url; + + 省略getter/setter + ...... +} +``` + +**注意**:当使用 `@PropertySource` 时,确保外部文件路径正确,且文件在类路径(classpath)中。 + +更多内容请查看我的这篇文章:[10 分钟搞定 SpringBoot 如何优雅读取配置文件?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486181&idx=2&sn=10db0ae64ef501f96a5b0dbc4bd78786&chksm=cea2452ef9d5cc384678e456427328600971180a77e40c13936b19369672ca3e342c26e92b50&token=816772476&lang=zh_CN#rd) 。 + +## MVC + +### HTTP 请求 + +**5 种常见的请求类型:** + +- **GET**:请求从服务器获取特定资源。举个例子:`GET /users`(获取所有学生) +- **POST**:在服务器上创建一个新的资源。举个例子:`POST /users`(创建学生) +- **PUT**:更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:`PUT /users/12`(更新编号为 12 的学生) +- **DELETE**:从服务器删除特定的资源。举个例子:`DELETE /users/12`(删除编号为 12 的学生) +- **PATCH**:更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。 + +#### GET 请求 + +`@GetMapping("users")` 等价于`@RequestMapping(value="/users",method=RequestMethod.GET)`。 + +```java +@GetMapping("/users") +public ResponseEntity> getAllUsers() { + return userRepository.findAll(); +} +``` + +#### POST 请求 + +`@PostMapping("users")` 等价于`@RequestMapping(value="/users",method=RequestMethod.POST)`。 + +`@PostMapping` 通常与 `@RequestBody` 配合,用于接收 JSON 数据并映射为 Java 对象。 + +```java +@PostMapping("/users") +public ResponseEntity createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) { + return userRepository.save(userCreateRequest); +} +``` + +#### PUT 请求 + +`@PutMapping("/users/{userId}")` 等价于`@RequestMapping(value="/users/{userId}",method=RequestMethod.PUT)`。 + +```java +@PutMapping("/users/{userId}") +public ResponseEntity updateUser(@PathVariable(value = "userId") Long userId, + @Valid @RequestBody UserUpdateRequest userUpdateRequest) { + ...... +} +``` + +#### DELETE 请求 + +`@DeleteMapping("/users/{userId}")`等价于`@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)` + +```java +@DeleteMapping("/users/{userId}") +public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){ + ...... +} +``` + +#### PATCH 请求 + +一般实际项目中,我们都是 PUT 不够用了之后才用 PATCH 请求去更新数据。 + +```java + @PatchMapping("/profile") + public ResponseEntity updateStudent(@RequestBody StudentUpdateRequest studentUpdateRequest) { + studentRepository.updateDetail(studentUpdateRequest); + return ResponseEntity.ok().build(); + } +``` + +### 参数绑定 + +在处理 HTTP 请求时,Spring MVC 提供了多种注解用于绑定请求参数到方法参数中。以下是常见的参数绑定方式: + +#### 从 URL 路径中提取参数 + +`@PathVariable` 用于从 URL 路径中提取参数。例如: + +```java +@GetMapping("/klasses/{klassId}/teachers") +public List getTeachersByClass(@PathVariable("klassId") Long klassId) { + return teacherService.findTeachersByClass(klassId); +} +``` + +若请求 URL 为 `/klasses/123/teachers`,则 `klassId = 123`。 + +#### 绑定查询参数 + +`@RequestParam` 用于绑定查询参数。例如: + +```java +@GetMapping("/klasses/{klassId}/teachers") +public List getTeachersByClass(@PathVariable Long klassId, + @RequestParam(value = "type", required = false) String type) { + return teacherService.findTeachersByClassAndType(klassId, type); +} +``` + +若请求 URL 为 `/klasses/123/teachers?type=web`,则 `klassId = 123`,`type = web`。 + +#### 绑定请求体中的 JSON 数据 + +`@RequestBody` 用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且**Content-Type 为 application/json** 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用`HttpMessageConverter`或者自定义的`HttpMessageConverter`将请求的 body 中的 json 字符串转换为 java 对象。 + +我用一个简单的例子来给演示一下基本使用! + +我们有一个注册的接口: + +```java +@PostMapping("/sign-up") +public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) { + userService.save(userRegisterRequest); + return ResponseEntity.ok().build(); +} +``` + +`UserRegisterRequest`对象: + +```java +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserRegisterRequest { + @NotBlank + private String userName; + @NotBlank + private String password; + @NotBlank + private String fullName; +} +``` + +我们发送 post 请求到这个接口,并且 body 携带 JSON 数据: + +```json +{ "userName": "coder", "fullName": "shuangkou", "password": "123456" } +``` + +这样我们的后端就可以直接把 json 格式的数据映射到我们的 `UserRegisterRequest` 类上。 + +![](./images/spring-annotations/@RequestBody.png) + +**注意**: + +- 一个方法只能有一个 `@RequestBody` 参数,但可以有多个 `@PathVariable` 和 `@RequestParam`。 +- 如果需要接收多个复杂对象,建议合并成一个单一对象。 + +## 数据校验 + +数据校验是保障系统稳定性和安全性的关键环节。即使在用户界面(前端)已经实施了数据校验,**后端服务仍必须对接收到的数据进行再次校验**。这是因为前端校验可以被轻易绕过(例如,通过开发者工具修改请求或使用 Postman、curl 等 HTTP 工具直接调用 API),恶意或错误的数据可能直接发送到后端。因此,后端校验是防止非法数据、维护数据一致性、确保业务逻辑正确执行的最后一道,也是最重要的一道防线。 + +Bean Validation 是一套定义 JavaBean 参数校验标准的规范 (JSR 303, 349, 380),它提供了一系列注解,可以直接用于 JavaBean 的属性上,从而实现便捷的参数校验。 + +- **JSR 303 (Bean Validation 1.0):** 奠定了基础,引入了核心校验注解(如 `@NotNull`、`@Size`、`@Min`、`@Max` 等),定义了如何通过注解的方式对 JavaBean 的属性进行校验,并支持嵌套对象校验和自定义校验器。 +- **JSR 349 (Bean Validation 1.1):** 在 1.0 基础上进行扩展,例如引入了对方法参数和返回值校验的支持、增强了对分组校验(Group Validation)的处理。 +- **JSR 380 (Bean Validation 2.0):** 拥抱 Java 8 的新特性,并进行了一些改进,例如支持 `java.time` 包中的日期和时间类型、引入了一些新的校验注解(如 `@NotEmpty`, `@NotBlank`等)。 + +Bean Validation 本身只是一套**规范(接口和注解)**,我们需要一个实现了这套规范的**具体框架**来执行校验逻辑。目前,**Hibernate Validator** 是 Bean Validation 规范最权威、使用最广泛的参考实现。 + +- Hibernate Validator 4.x 实现了 Bean Validation 1.0 (JSR 303)。 +- Hibernate Validator 5.x 实现了 Bean Validation 1.1 (JSR 349)。 +- Hibernate Validator 6.x 及更高版本实现了 Bean Validation 2.0 (JSR 380)。 + +在 Spring Boot 项目中使用 Bean Validation 非常方便,这得益于 Spring Boot 的自动配置能力。关于依赖引入,需要注意: + +- 在较早版本的 Spring Boot(通常指 2.3.x 之前)中,`spring-boot-starter-web` 依赖默认包含了 hibernate-validator。因此,只要引入了 Web Starter,就无需额外添加校验相关的依赖。 +- 从 Spring Boot 2.3.x 版本开始,为了更精细化的依赖管理,校验相关的依赖被移出了 spring-boot-starter-web。如果你的项目使用了这些或更新的版本,并且需要 Bean Validation 功能,那么你需要显式地添加 `spring-boot-starter-validation` 依赖: + +```xml + + org.springframework.boot + spring-boot-starter-validation + +``` + +![](https://oss.javaguide.cn/2021/03/c7bacd12-1c1a-4e41-aaaf-4cad840fc073.png) + +非 SpringBoot 项目需要自行引入相关依赖包,这里不多做讲解,具体可以查看我的这篇文章:[如何在 Spring/Spring Boot 中做参数校验?你需要了解的都在这里!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485783&idx=1&sn=a407f3b75efa17c643407daa7fb2acd6&chksm=cea2469cf9d5cf8afbcd0a8a1c9cc4294d6805b8e01bee6f76bb2884c5bc15478e91459def49&token=292197051&lang=zh_CN#rd)。 + +👉 需要注意的是:所有的注解,推荐使用 JSR 注解,即`javax.validation.constraints`,而不是`org.hibernate.validator.constraints` + +### 一些常用的字段验证的注解 + +Bean Validation 规范及其实现(如 Hibernate Validator)提供了丰富的注解,用于声明式地定义校验规则。以下是一些常用的注解及其说明: + +- `@NotNull`: 检查被注解的元素(任意类型)不能为 `null`。 +- `@NotEmpty`: 检查被注解的元素(如 `CharSequence`、`Collection`、`Map`、`Array`)不能为 `null` 且其大小/长度不能为 0。注意:对于字符串,`@NotEmpty` 允许包含空白字符的字符串,如 `" "`。 +- `@NotBlank`: 检查被注解的 `CharSequence`(如 `String`)不能为 `null`,并且去除首尾空格后的长度必须大于 0。(即,不能为空白字符串)。 +- `@Null`: 检查被注解的元素必须为 `null`。 +- `@AssertTrue` / `@AssertFalse`: 检查被注解的 `boolean` 或 `Boolean` 类型元素必须为 `true` / `false`。 +- `@Min(value)` / `@Max(value)`: 检查被注解的数字类型(或其字符串表示)的值必须大于等于 / 小于等于指定的 `value`。适用于整数类型(`byte`、`short`、`int`、`long`、`BigInteger` 等)。 +- `@DecimalMin(value)` / `@DecimalMax(value)`: 功能类似 `@Min` / `@Max`,但适用于包含小数的数字类型(`BigDecimal`、`BigInteger`、`CharSequence`、`byte`、`short`、`int`、`long`及其包装类)。 `value` 必须是数字的字符串表示。 +- `@Size(min=, max=)`: 检查被注解的元素(如 `CharSequence`、`Collection`、`Map`、`Array`)的大小/长度必须在指定的 `min` 和 `max` 范围之内(包含边界)。 +- `@Digits(integer=, fraction=)`: 检查被注解的数字类型(或其字符串表示)的值,其整数部分的位数必须 ≤ `integer`,小数部分的位数必须 ≤ `fraction`。 +- `@Pattern(regexp=, flags=)`: 检查被注解的 `CharSequence`(如 `String`)是否匹配指定的正则表达式 (`regexp`)。`flags` 可以指定匹配模式(如不区分大小写)。 +- `@Email`: 检查被注解的 `CharSequence`(如 `String`)是否符合 Email 格式(内置了一个相对宽松的正则表达式)。 +- `@Past` / `@Future`: 检查被注解的日期或时间类型(`java.util.Date`、`java.util.Calendar`、JSR 310 `java.time` 包下的类型)是否在当前时间之前 / 之后。 +- `@PastOrPresent` / `@FutureOrPresent`: 类似 `@Past` / `@Future`,但允许等于当前时间。 +- …… + +### 验证请求体(RequestBody) + +当 Controller 方法使用 `@RequestBody` 注解来接收请求体并将其绑定到一个对象时,可以在该参数前添加 `@Valid` 注解来触发对该对象的校验。如果验证失败,它将抛出`MethodArgumentNotValidException`。 + +```java +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Person { + @NotNull(message = "classId 不能为空") + private String classId; + + @Size(max = 33) + @NotNull(message = "name 不能为空") + private String name; + + @Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围") + @NotNull(message = "sex 不能为空") + private String sex; + + @Email(message = "email 格式不正确") + @NotNull(message = "email 不能为空") + private String email; +} + + +@RestController +@RequestMapping("/api") +public class PersonController { + @PostMapping("/person") + public ResponseEntity getPerson(@RequestBody @Valid Person person) { + return ResponseEntity.ok().body(person); + } +} +``` + +### 验证请求参数(Path Variables 和 Request Parameters) + +对于直接映射到方法参数的简单类型数据(如路径变量 `@PathVariable` 或请求参数 `@RequestParam`),校验方式略有不同: + +1. **在 Controller 类上添加 `@Validated` 注解**:这个注解是 Spring 提供的(非 JSR 标准),它使得 Spring 能够处理方法级别的参数校验注解。**这是必需步骤。** +2. **将校验注解直接放在方法参数上**:将 `@Min`, `@Max`, `@Size`, `@Pattern` 等校验注解直接应用于对应的 `@PathVariable` 或 `@RequestParam` 参数。 + +一定一定不要忘记在类上加上 `@Validated` 注解了,这个参数可以告诉 Spring 去校验方法参数。 + +```java +@RestController +@RequestMapping("/api") +@Validated // 关键步骤 1: 必须在类上添加 @Validated +public class PersonController { + + @GetMapping("/person/{id}") + public ResponseEntity getPersonByID( + @PathVariable("id") + @Max(value = 5, message = "ID 不能超过 5") // 关键步骤 2: 校验注解直接放在参数上 + Integer id + ) { + // 如果传入的 id > 5,Spring 会在进入方法体前抛出 ConstraintViolationException 异常。 + // 全局异常处理器同样需要处理此异常。 + return ResponseEntity.ok().body(id); + } + + @GetMapping("/person") + public ResponseEntity findPersonByName( + @RequestParam("name") + @NotBlank(message = "姓名不能为空") // 同样适用于 @RequestParam + @Size(max = 10, message = "姓名长度不能超过 10") + String name + ) { + return ResponseEntity.ok().body("Found person: " + name); + } +} +``` + +## 全局异常处理 + +介绍一下我们 Spring 项目必备的全局处理 Controller 层异常。 + +**相关注解:** + +1. `@ControllerAdvice` :注解定义全局异常处理类 +2. `@ExceptionHandler` :注解声明异常处理方法 + +如何使用呢?拿我们在第 5 节参数校验这块来举例子。如果方法参数不对的话就会抛出`MethodArgumentNotValidException`,我们来处理这个异常。 + +```java +@ControllerAdvice +@ResponseBody +public class GlobalExceptionHandler { + + /** + * 请求参数异常处理 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) { + ...... + } +} +``` + +更多关于 Spring Boot 异常处理的内容,请看我的这两篇文章: + +1. [SpringBoot 处理异常的几种常见姿势](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485568&idx=2&sn=c5ba880fd0c5d82e39531fa42cb036ac&chksm=cea2474bf9d5ce5dcbc6a5f6580198fdce4bc92ef577579183a729cb5d1430e4994720d59b34&token=2133161636&lang=zh_CN#rd) +2. [使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486379&idx=2&sn=48c29ae65b3ed874749f0803f0e4d90e&chksm=cea24460f9d5cd769ed53ad7e17c97a7963a89f5350e370be633db0ae8d783c3a3dbd58c70f8&token=1054498516&lang=zh_CN#rd) + +## 事务 + +在要开启事务的方法上使用`@Transactional`注解即可! + +```java +@Transactional(rollbackFor = Exception.class) +public void save() { + ...... +} + +``` + +我们知道 Exception 分为运行时异常 RuntimeException 和非运行时异常。在`@Transactional`注解中如果不配置`rollbackFor`属性,那么事务只会在遇到`RuntimeException`的时候才会回滚,加上`rollbackFor=Exception.class`,可以让事务在遇到非运行时异常时也回滚。 + +`@Transactional` 注解一般可以作用在`类`或者`方法`上。 + +- **作用于类**:当把`@Transactional` 注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息。 +- **作用于方法**:当类配置了`@Transactional`,方法也配置了`@Transactional`,方法的事务会覆盖类的事务配置信息。 + +更多关于 Spring 事务的内容请查看我的这篇文章:[可能是最漂亮的 Spring 事务管理详解](./spring-transaction.md) 。 + +## JPA + +Spring Data JPA 提供了一系列注解和功能,帮助开发者轻松实现 ORM(对象关系映射)。 + +### 创建表 + +`@Entity` 用于声明一个类为 JPA 实体类,与数据库中的表映射。`@Table` 指定实体对应的表名。 + +```java +@Entity +@Table(name = "role") +public class Role { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private String description; + + // 省略 getter/setter +} +``` + +### 主键生成策略 + +`@Id`声明字段为主键。`@GeneratedValue` 指定主键的生成策略。 + +JPA 提供了 4 种主键生成策略: + +- **`GenerationType.TABLE`**:通过数据库表生成主键。 +- **`GenerationType.SEQUENCE`**:通过数据库序列生成主键(适用于 Oracle 等数据库)。 +- **`GenerationType.IDENTITY`**:主键自增长(适用于 MySQL 等数据库)。 +- **`GenerationType.AUTO`**:由 JPA 自动选择合适的生成策略(默认策略)。 + +```java +@Id +@GeneratedValue(strategy = GenerationType.IDENTITY) +private Long id; +``` + +通过 `@GenericGenerator` 声明自定义主键生成策略: + +```java +@Id +@GeneratedValue(generator = "IdentityIdGenerator") +@GenericGenerator(name = "IdentityIdGenerator", strategy = "identity") +private Long id; +``` + +等价于: + +```java +@Id +@GeneratedValue(strategy = GenerationType.IDENTITY) +private Long id; +``` + +JPA 提供的主键生成策略有如下几种: + +```java +public class DefaultIdentifierGeneratorFactory + implements MutableIdentifierGeneratorFactory, Serializable, ServiceRegistryAwareService { + + @SuppressWarnings("deprecation") + public DefaultIdentifierGeneratorFactory() { + register( "uuid2", UUIDGenerator.class ); + register( "guid", GUIDGenerator.class ); // can be done with UUIDGenerator + strategy + register( "uuid", UUIDHexGenerator.class ); // "deprecated" for new use + register( "uuid.hex", UUIDHexGenerator.class ); // uuid.hex is deprecated + register( "assigned", Assigned.class ); + register( "identity", IdentityGenerator.class ); + register( "select", SelectGenerator.class ); + register( "sequence", SequenceStyleGenerator.class ); + register( "seqhilo", SequenceHiLoGenerator.class ); + register( "increment", IncrementGenerator.class ); + register( "foreign", ForeignGenerator.class ); + register( "sequence-identity", SequenceIdentityGenerator.class ); + register( "enhanced-sequence", SequenceStyleGenerator.class ); + register( "enhanced-table", TableGenerator.class ); + } + + public void register(String strategy, Class generatorClass) { + LOG.debugf( "Registering IdentifierGenerator strategy [%s] -> [%s]", strategy, generatorClass.getName() ); + final Class previous = generatorStrategyToClassNameMap.put( strategy, generatorClass ); + if ( previous != null ) { + LOG.debugf( " - overriding [%s]", previous.getName() ); + } + } + +} +``` + +### 字段映射 + +`@Column` 用于指定实体字段与数据库列的映射关系。 + +- **`name`**:指定数据库列名。 +- **`nullable`**:指定是否允许为 `null`。 +- **`length`**:设置字段的长度(仅适用于 `String` 类型)。 +- **`columnDefinition`**:指定字段的数据库类型和默认值。 + +```java +@Column(name = "user_name", nullable = false, length = 32) +private String userName; + +@Column(columnDefinition = "tinyint(1) default 1") +private Boolean enabled; +``` + +### 忽略字段 + +`@Transient` 用于声明不需要持久化的字段。 + +```java +@Entity +public class User { + + @Transient + private String temporaryField; // 不会映射到数据库表中 +} +``` + +其他不被持久化的字段方式: + +- **`static`**:静态字段不会被持久化。 +- **`final`**:最终字段不会被持久化。 +- **`transient`**:使用 Java 的 `transient` 关键字声明的字段不会被序列化或持久化。 + +### 大字段存储 + +`@Lob` 用于声明大字段(如 `CLOB` 或 `BLOB`)。 + +```java +@Lob +@Column(name = "content", columnDefinition = "LONGTEXT NOT NULL") +private String content; +``` + +### 枚举类型映射 + +`@Enumerated` 用于将枚举类型映射为数据库字段。 + +- **`EnumType.ORDINAL`**:存储枚举的序号(默认)。 +- **`EnumType.STRING`**:存储枚举的名称(推荐)。 + +```java +public enum Gender { + MALE, + FEMALE +} + +@Entity +public class User { + + @Enumerated(EnumType.STRING) + private Gender gender; +} +``` + +数据库中存储的值为 `MALE` 或 `FEMALE`。 + +### 审计功能 + +通过 JPA 的审计功能,可以在实体中自动记录创建时间、更新时间、创建人和更新人等信息。 + +审计基类: + +```java +@Data +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class AbstractAuditBase { + + @CreatedDate + @Column(updatable = false) + private Instant createdAt; + + @LastModifiedDate + private Instant updatedAt; + + @CreatedBy + @Column(updatable = false) + private String createdBy; + + @LastModifiedBy + private String updatedBy; +} +``` + +配置审计功能: + +```java +@Configuration +@EnableJpaAuditing +public class AuditConfig { + + @Bean + public AuditorAware auditorProvider() { + return () -> Optional.ofNullable(SecurityContextHolder.getContext()) + .map(SecurityContext::getAuthentication) + .filter(Authentication::isAuthenticated) + .map(Authentication::getName); + } +} +``` + +简单介绍一下上面涉及到的一些注解: + +1. `@CreatedDate`: 表示该字段为创建时间字段,在这个实体被 insert 的时候,会设置值 +2. `@CreatedBy` :表示该字段为创建人,在这个实体被 insert 的时候,会设置值 `@LastModifiedDate`、`@LastModifiedBy`同理。 +3. `@EnableJpaAuditing`:开启 JPA 审计功能。 + +### 修改和删除操作 + +`@Modifying` 注解用于标识修改或删除操作,必须与 `@Transactional` 一起使用。 + +```java +@Repository +public interface UserRepository extends JpaRepository { + + @Modifying + @Transactional + void deleteByUserName(String userName); +} +``` + +### 关联关系 + +JPA 提供了 4 种关联关系的注解: + +- **`@OneToOne`**:一对一关系。 +- **`@OneToMany`**:一对多关系。 +- **`@ManyToOne`**:多对一关系。 +- **`@ManyToMany`**:多对多关系。 + +```java +@Entity +public class User { + + @OneToOne + private Profile profile; + + @OneToMany(mappedBy = "user") + private List orders; +} +``` + +## JSON 数据处理 + +在 Web 开发中,经常需要处理 Java 对象与 JSON 格式之间的转换。Spring 通常集成 Jackson 库来完成此任务,以下是一些常用的 Jackson 注解,可以帮助我们定制化 JSON 的序列化(Java 对象转 JSON)和反序列化(JSON 转 Java 对象)过程。 + +### 过滤 JSON 字段 + +有时我们不希望 Java 对象的某些字段被包含在最终生成的 JSON 中,或者在将 JSON 转换为 Java 对象时不处理某些 JSON 属性。 + +`@JsonIgnoreProperties` 作用在类上用于过滤掉特定字段不返回或者不解析。 + +```java +// 在生成 JSON 时忽略 userRoles 属性 +// 如果允许未知属性(即 JSON 中有而类中没有的属性),可以添加 ignoreUnknown = true +@JsonIgnoreProperties({"userRoles"}) +public class User { + private String userName; + private String fullName; + private String password; + private List userRoles = new ArrayList<>(); + // getters and setters... +} +``` + +`@JsonIgnore`作用于字段或`getter/setter` 方法级别,用于指定在序列化或反序列化时忽略该特定属性。 + +```java +public class User { + private String userName; + private String fullName; + private String password; + + // 在生成 JSON 时忽略 userRoles 属性 + @JsonIgnore + private List userRoles = new ArrayList<>(); + // getters and setters... +} +``` + +`@JsonIgnoreProperties` 更适用于在类定义时明确排除多个字段,或继承场景下的字段排除;`@JsonIgnore` 则更直接地用于标记单个具体字段。 + +### 格式化 JSON 数据 + +`@JsonFormat` 用于指定属性在序列化和反序列化时的格式。常用于日期时间类型的格式化。 + +比如: + +```java +// 指定 Date 类型序列化为 ISO 8601 格式字符串,并设置时区为 GMT +@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "GMT") +private Date date; +``` + +### 扁平化 JSON 对象 + +`@JsonUnwrapped` 注解作用于字段上,用于在序列化时将其嵌套对象的属性“提升”到当前对象的层级,反序列化时执行相反操作。这可以使 JSON 结构更扁平。 + +假设有 `Account` 类,包含 `Location` 和 `PersonInfo` 两个嵌套对象。 + +```java +@Getter +@Setter +@ToString +public class Account { + private Location location; + private PersonInfo personInfo; + + @Getter + @Setter + @ToString + public static class Location { + private String provinceName; + private String countyName; + } + @Getter + @Setter + @ToString + public static class PersonInfo { + private String userName; + private String fullName; + } +} + +``` + +未扁平化之前的 JSON 结构: + +```json +{ + "location": { + "provinceName": "湖北", + "countyName": "武汉" + }, + "personInfo": { + "userName": "coder1234", + "fullName": "shaungkou" + } +} +``` + +使用`@JsonUnwrapped` 扁平对象: + +```java +@Getter +@Setter +@ToString +public class Account { + @JsonUnwrapped + private Location location; + @JsonUnwrapped + private PersonInfo personInfo; + ...... +} +``` + +扁平化后的 JSON 结构: + +```json +{ + "provinceName": "湖北", + "countyName": "武汉", + "userName": "coder1234", + "fullName": "shaungkou" +} +``` + +## 测试 + +`@ActiveProfiles`一般作用于测试类上, 用于声明生效的 Spring 配置文件。 + +```java +// 指定在 RANDOM_PORT 上启动应用上下文,并激活 "test" profile +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +@Slf4j +public abstract class TestBase { + // Common test setup or abstract methods... +} +``` + +`@Test` 是 JUnit 框架(通常是 JUnit 5 Jupiter)提供的注解,用于标记一个方法为测试方法。虽然不是 Spring 自身的注解,但它是执行单元测试和集成测试的基础。 + +`@Transactional`被声明的测试方法的数据会回滚,避免污染测试数据。 + +`@WithMockUser` 是 Spring Security Test 模块提供的注解,用于在测试期间模拟一个已认证的用户。可以方便地指定用户名、密码、角色(authorities)等信息,从而测试受安全保护的端点或方法。 + +```java +public class MyServiceTest extends TestBase { // Assuming TestBase provides Spring context + + @Test + @Transactional // 测试数据将回滚 + @WithMockUser(username = "test-user", authorities = { "ROLE_TEACHER", "read" }) // 模拟一个名为 "test-user",拥有 TEACHER 角色和 read 权限的用户 + void should_perform_action_requiring_teacher_role() throws Exception { + // ... 测试逻辑 ... + // 这里可以调用需要 "ROLE_TEACHER" 权限的服务方法 + } +} +``` + + + +## 注解分类总结 + +(表格) + + From 9fe4f7ce7da4d36ccfe4d67f4e83924ba958e5dd Mon Sep 17 00:00:00 2001 From: Senrian <47714364+Senrian@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:13:13 +0800 Subject: [PATCH 7/7] fix: remove duplicate content in agent-basis.md (fix #2808) Removed 14 duplicate lines from the Agentic Workflows section. --- docs/ai/agent/agent-basis.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/docs/ai/agent/agent-basis.md b/docs/ai/agent/agent-basis.md index 5948bc962b1..b240b321bc1 100644 --- a/docs/ai/agent/agent-basis.md +++ b/docs/ai/agent/agent-basis.md @@ -496,20 +496,6 @@ Multi-Agent 系统是指多个独立 Agent 通过协作完成单一复杂任务 **通俗理解:** Agentic Workflows 告诉我们,构建强大的 AI 应用,并不是必须要等 GPT-5 或更底层的参数突破,而是用后端工程的思维,将“推理、记忆、反思、多实体协作”编排成一条流水线。这也是当前 AI 落地应用从“玩具”走向“工业级生产力”的最成熟路径。背景与演进 -### AI Agent 六代进化史 - -还记得第一次被 ChatGPT 震撼的时刻吗?那时它还是个需要你费尽心思写提示词的“静态百科全书”。 - -然而短短三年过去,AI 的进化速度早已超越了我们的想象——它不仅长出了“四肢”,学会了自己调用工具、自己操作电脑屏幕,甚至正在朝着 24 小时全自动打工的“数字实体”狂奔! - -从最初的“被动响应”到未来的“具身智能”,AI Agent(智能体)到底经历了怎样的疯狂迭代?今天,我们就来一次性硬核梳理 **AI Agent 的六代进化史**。带你看懂 AI 从聊天工具到超级生产力的终极演进路线图!👇 - -1. **第 0 代(2022年底):被动响应。** 以 ChatGPT 为代表,依赖提示词工程(Prompt Engineering),本质是“静态知识预言机”,无法感知实时世界且缺乏行动能力。 -2. **第 1 代(2023年中):工具觉醒。** 引入 Function Calling (允许模型调用外部API)和 RAG 技术(增强外部知识检索,虽 2020 年提出,但 2023 年广泛应用),赋予 AI “执行四肢”与外部记忆。AutoGPT 是早期代理尝试,但确实因无限循环和缺乏可靠规划而效率低(常被称为“hallucination-prone”)。 -3. **第 2 代(2023年底):工程化编排。** 确立 ReAct 推理框架,推广多智能体协作模式。Coze、Dify 等低代码平台降低了开发门槛,强调流程的可控性。这代强调从混乱自治到工程化,如通过DAG(有向无环图)避免AutoGPT的低效。 -4. **第 3 代(2024年底):标准化与多模态。** MCP 协议(Model Context Protocol)终结了集成碎片化,Computer Use 允许 Agent 通过屏幕、鼠标、键盘交互图形界面(多模态扩展)。Cursor 等 AI 编程工具推动了“Vibe Coding”(氛围编程,使用 AI 根据自然语言提示生成功能代码)。 -5. **第 4 代(2025年底):常驻自治。** 核心是 Agent Skills 技能封装和 Heartbeat 心跳机制(OpenClaw、Moltbook等普及),使 Agent 成为 24 小时后台运行、具备本地数据主权的“数字实体”。 -6. **第 5 代(前瞻):闭环与具身。** 进化方向为内建记忆、具备预测能力的世界模型,并从数字世界扩展至物理机器人领域。 ### ⭐️ Agent、传统编程、Workflow 三者的本质区别是什么?