Lucaju 的技术笔记 - 框架与中间件
type: channel
link: https://www.lucaju.cn/index.php/category/framework/
description: Spring 全家桶（Spring Boot、Spring Cloud、Spring Security）、MyBatis/MyBatis-Plus 实战、Redis 缓存设计（过期策略、分布式锁）、消息队列（RabbitMQ/Kafka 使用场景与问题排查）等
items: 分布式智能体｜A2A Agent实战
items: 吃透 Spring AI Alibaba 多智能体｜四大协同模式+完整代码
items: Spring AI Alibaba + MCP：调用MCP市场公开服务实操
items: 一文吃透 Spring AI Alibaba + MCP：服务端搭建 + 客户端调用全流程
items: Agent Skills | Spring Ai Alibaba从零构建可扩展 AI 智能体
items: Spring AI Alibaba 人工介入实战｜Human-in-the-Loop 让 AI 更可靠
items: 使用Spring AI Alibaba构建智能体Agent
items: 详细解析Spring如何解决循环依赖问题
items: Jakarta Validation 优雅实现参数校验：从基础使用到自定义扩展
items: Spring Retry 重试机制：优雅解决接口调用失败问题

分布式智能体｜A2A Agent实战
type: item
date: 2026-04-02T15:55:00+08:00
description: 随着智能体应用的广泛应用，智能体的分布式部署、跨网络、跨框架、跨组织调用，成为当下智能体落地急需解决的问题。针对这一痛点，Google推出了Agent2Agent（简称A2A）协议，专为智能体之间的互联互通、协同协作提供标准化的解决方案。本文基于Spring AI Alibaba框架，使用Nacos作为注册中心，落地完整的分布式智能体方案。整体架构：一、Nacos注册中心安装本次选用的Nacos版本为3.2.0，安装配置流程可参考官方文档，这里不再赘述基础安装步骤。Nacos官方地址：https://nacos.io/二、项目依赖引入使用官方推荐的BOM方式进行依赖管理<dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-bom</artifactId> <version>1.1.2.0</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.1.2</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-extensions-bom</artifactId> <version>1.1.2.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-agent-framework</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-a2a-nacos</artifactId> </dependency> </dependencies> 三、Agent服务注册配置完成依赖引入后，需要将自定义的智能体交由Spring容器管理，同时配置Nacos注册参数，实现智能体的自动注册。1. 配置Bean对象本次以讲笑话的智能体jokeAgent为例@Configuration public class A2AAgentConfig { @Bean(name = "jokeAgent") public ReactAgent jokeAgent() { // 构建通义千问API对象 DashScopeApi dashScopeApi = DashScopeApi.builder() .apiKey(System.getenv("AliQwen_API")) .build(); // 初始化对话模型 DashScopeChatModel chatModel = DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) .defaultOptions(DashScopeChatOptions.builder() .model(DashScopeChatModel.DEFAULT_MODEL_NAME) .temperature(0.5) .maxToken(1000) .build()) .build(); // 创建并返回笑话智能体 return ReactAgent.builder() .name("jokeAgent") .model(chatModel) .description("负责讲笑话。") .instruction…

吃透 Spring AI Alibaba 多智能体｜四大协同模式+完整代码
type: item
date: 2026-03-30T13:21:00+08:00
description: 当一个Agent要处理很多复杂的事情，就会出现效果不佳的情况，这时候Multi-Agent就可以将复杂任务进行拆分，更好的完成我们的任务。本文基于Spring AI Alibaba框架，详解Multi-Agent的核心概念、占位符用法，以及顺序执行、并行执行、路由调度、监督者管控四大核心模式，搭配完整可运行的代码示例，快速上手多智能体开发。一、Multi-Agent 核心概念官方对Multi-Agent的定义清晰直白：Multi-agent 将复杂的应用程序分解为多个协同工作的专业化Agent。与依赖单个Agent处理所有步骤不同，Multi-agent架构允许你将更小、更专注的Agent组合成协调的工作流。Multi-agent系统在以下情况下很有用：单个Agent拥有太多工具，难以做出正确的工具选择决策上下文或记忆增长过大，单个Agent难以有效跟踪任务需要专业化（例如：规划器、研究员、数学专家）二、指令占位符（Instruction）用法支持使用占位符来动态引用状态中的数据。比如AgentB需要使用AgentA处理后的数据，就需要通过Instruction来获取1、支持的占位符占位符说明使用场景{input}用户输入的原始内容第一个Agent或需要用户输入的 Agent{outputKey}引用其他Agent通过 outputKey 存储的输出顺序执行中，后续Agent引用前面Agent的输出{stateKey}引用状态中的任意键值访问状态中的任何数据2、占位符工作原理自动替换：系统会在执行 Agent 的 instruction 时，自动将占位符替换为对应的实际值状态查找：占位符会从当前状态（OverAllState）中查找对应的值类型安全：占位符的值会被转换为字符串并插入到 instruction 中三、公共代码（ChatModel获取）我先把获取ChatModel这个公共代码放在这里，后面就不重复写进来了默认模型qwen-pluspublic static ChatModel getChatModel() { DashScopeApi dashScopeApi = DashScopeApi.builder() .apiKey(System.getenv("AliQwen_API")) .build(); return DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) .defaultOptions(DashScopeChatOptions.builder() // Note: model must be set when use options build. .model(DashScopeChatModel.DEFAULT_MODEL_NAME) .temperature(0.5) .maxToken(1000) .build()) .build(); }四、顺序执行（Sequential Agent）顾名思义，多个Agent按照顺序依次执行，上一个Agent的输出结果可以传递给下一个Agent1. 执行流程Agent A处理初始输入Agent A的输出传递给Agent BAgent B处理并传递给Agent C最后一个Agent返回最终结果2. 示例代码import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import com.alibaba.cloud.ai.graph.NodeOutput; import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent; import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.spring…

Spring AI Alibaba + MCP：调用MCP市场公开服务实操
type: item
date: 2026-03-21T21:08:00+08:00
description: 上一篇博客，我们介绍了如何将本地工具封装为 MCP 服务，并成功创建客户端实现连接，感兴趣的朋友可以回顾：https://www.lucaju.cn/index.php/archives/169/今天，我们聚焦实操——如何调用 MCP 市场上的公开服务，以「高德地图 MCP 服务」为例，步骤清晰可复现，新手也能快速上手。一、先了解：MCP 公开服务市场首先给大家推荐一个优质的 MCP 公开服务平台：https://mcp.so/zh这个平台类似 MCP 服务的「GitHub」，目前已收录超过一万八千个公开 MCP 服务，涵盖地图、工具、接口等各类场景，我们今天要用的高德地图 MCP 服务也收录其中。高德地图 MCP 服务直达链接：https://mcp.so/zh/server/amap-maps/amap进入链接后，注意保存页面中的「服务器配置 JSON」（如下所示），后续配置项目时会直接用到，重点留意 env 中的 AMAP_MAPS_API_KEY 字段。二、前置准备：申请高德 API-Key调用高德地图 MCP 服务，需先获取个人 API-Key，步骤如下（3步搞定）：1. 登录高德开放平台访问高德开放平台：https://lbs.amap.com/ ，注册一个用户，完成认证后登录到后台2. 创建一个应用3. 生成 API-Key注意：服务平台选择Web服务，填写完成后提交，即可生成 API-Key，保存好该 Key，后续替换配置使用。三、编写客户端代码连接高德服务前置准备完成后，开始配置项目、编写代码，全程分为3个步骤，也很简单操作。1. 拷贝高德MCP服务中的json文件到项目中注意: 替换api_key，使用我们刚刚创建好的api_key{ "mcpServers": { "amap-maps": { "command": "npx", "args": [ "-y", "@amap/amap-maps-mcp-server" ], "env": { "AMAP_MAPS_API_KEY": "api_key" } } } }把这个json放到项目resource目录下，命名为 mcp-servers-config.json （可自定义，但需与后续配置对应）2. 修改application.yml配置文件添加 MCP 客户端配置，核心是指定上述 JSON 文件的路径spring: application: name: spring-ai-alibaba-agent ai: dashscope: api-key: ${AliQwen_API} mcp: client: type: async request-timeout: 10s toolcallback: enabled: true stdio: servers-configuration: classpath:/mcp-servers-config.json3. 编写测试代码测试代码其实和上一篇的客户端代码基本没有什么改动，我就简单放上来一些啦～@GetMapping("mcpTest") private void mcpTest() throws GraphRunnerException { ChatModel chatModel = getChatModel(); ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks(); System.out.printf(""" =====Find the tools from spring ToolCallbackProvider===== %s """, JSON.toJSONString(toolCallbacks)); // 构建智能体并绑定mcp服务 ReactAgent agent = ReactAgent.builder() .name("ip_search") .model(chatModel) .description("你是一个天气查询助手") .saver(new MemorySaver()) .toolCallbackProviders(toolCallbackProvider) .build(); // 运行时配置 RunnableConfig config = RunnableConfig.builder() .threadId("session") .build(); // 流式调用agent Flux<NodeOutput> stream = agent.stream("上海未来天气怎么样", config); StringBuffer answerString = new StringBuffer();…

一文吃透 Spring AI Alibaba + MCP：服务端搭建 + 客户端调用全流程
type: item
date: 2026-03-19T16:22:00+08:00
description: 一、MCP概念介绍MCP（Model Context Protocol，模型上下文协议）是 Anthropic 于 2024 年推出的AI 领域统一连接协议，被称为 “AI 的 USB-C 接口”，核心是让大模型（LLM）通过标准化方式安全、灵活地调用外部工具、数据库、API 与文件系统，打破数据孤岛。从架构来看，MCP基于C/S（客户端-服务端）模式实现，因此要完成MCP调用，需分别搭建MCP服务端（暴露工具方法）和MCP客户端（调用服务端方法）。本文将基于Spring Ai Alibaba生态，完整实现“本地方法封装为MCP服务 + 客户端调用MCP服务”的全流程，步骤清晰、可直接落地。二、Spring AI MCP的介绍Spring AI MCP 为模型上下文协议提供 Java 和 Spring 框架集成。它使 Spring AI 应用程序能够通过标准化的接口与不同的数据源和工具进行交互，支持同步和异步通信模式。整体架构如下：三、搭建本地MCP服务端1. 添加依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webflux</artifactId> <version>1.1.2</version> </dependency>注意：此处未使用常规的spring-boot-starter-web（内置Tomcat），因为spring-ai-starter-mcp-server-webflux与Tomcat存在冲突。使用spring-boot-starter会默认通过Netty启动服务，适配MCP服务端要求。2. 配置服务端application.ymlserver: port: 8088 # 服务端口，可自定义 servlet: encoding: enabled: true force: true charset: UTF-8 # 避免中文乱码 spring: application: name: local-mcp-server # 服务端应用名称 ai: mcp: server: type: async # 异步模式，提升调用性能 name: local-mcp-server # MCP服务名称 version: 1.0.0 # 服务版本3. 添加工具方法创建工具类，将需要对外暴露的方法用@Tool注解标记，并将该类交给Spring容器管理：@Service public class WeatherService { /** * 根据城市名称获取天气信息 * @param city 城市名称 * @return 天气描述 */ @Tool(description = "根据城市名称获取天气信息") public String getWeatherByCity(String city) { return city + " 今天天气很好！"; } }4. 添加MCP服务配置（McpServerConfig）创建配置类，通过ToolCallbackProvider将工具类（WeatherService）封装为MCP服务：@Configuration public class McpServerConfig { @Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } }5. 启动mcp-server 服务启动Spring Boot应用，查看控制台输出，确认服务启动成功（重点关注Netty启动信息）：2026-03-19T14:33:17.730+08:00 INFO 35517 --- [local-mcp-server] [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8088 (http) 2026-03-19T14:33:17.734+08:00 INFO 35517 --- [local-mcp-server] [ main] com.jcq.server.McpServerAppli…

Agent Skills | Spring Ai Alibaba从零构建可扩展 AI 智能体
type: item
date: 2026-03-18T13:32:04+08:00
description: 在当今AI应用开发领域，让AI智能体具备灵活的工具调用能力和可扩展的技能体系，是落地复杂业务场景的核心挑战。Spring AI Alibaba 原生提供了完善的 Skill 技能支持，能让智能体实现「技能发现→按需加载→工具执行」的全流程自动化。本文将从核心概念、组件解析、实战场景、最佳实践四个维度，手把手教你掌握 Spring AI Alibaba Skill 的使用方法。一、核心概念1.1 渐进式披露（核心设计思想）Spring AI Alibaba Skill 采用渐进式披露机制，最大化提升模型效率：系统初始仅注入技能元数据（名称、描述、路径）；模型判断需要使用某技能时，调用 read_skill(skill_name) 加载完整的 SKILL.md 文档；最后按需访问技能资源、执行绑定工具。1.2 Skill 标准目录结构每个技能独立为一个子目录，SKILL.md 为强制必需文件，目录结构如下：skills/ # 技能根目录 └── pdf-extractor/ # 单个技能目录（自定义命名） ├── SKILL.md # ✅ 必需：技能核心定义文档 ├── references/ # 可选：技能参考资料 ├── examples/ # 可选：使用示例 └── scripts/ # 可选：执行脚本1.3 SKILL.md 格式规范SKILL.md 采用元数据+正文结构，元数据通过 --- 包裹，是模型识别技能的关键：--- name: pdf-extractor # 必需：小写字母、数字、连字符，最长64字符 description: 用于从PDF文档中提取文本、表格和表单数据 # 必需：简洁描述，超长会被截断 --- # PDF 提取技能 ## 功能说明 You are a PDF extraction specialist. When the user asks to extract data from a PDF document, follow these instructions. ## 使用方法 1. **Validate Input** - Confirm the PDF file path is provided. - The default path for the pdf file is the current working directory. - Use the `shell` or `read_file` tool to check if the file exists - Verify it's a valid PDF format 2. **Extract Content** - Execute the extraction script using the `shell` tool: ```bash python scripts/extract_pdf.py <pdf_file_path> ``` - The script will output JSON format with extracted data 3. **Process Results** - Parse the JSON output from the script - Structure the data in a readable format - Handle any encoding issues (UTF-8, special characters) 4. **Present Output** - Summarize what was extracted - Present data in the requested format (JSON, Markdown, plain text) - Highlight any issues or limitations ## Script Location The extraction script is located at: `scripts/extract_pdf.py` ## Output Format The script returns JSON: ```json { "success": true, "filename": "report.pdf", "text": "Full text content...", "page_count": 10, "tables": [ { "page": 1, "data": [["Header1", "Header2"], ["Value1", "Value2"]] } ], "metadata": { "title": "Document Title", "author": "Author Name", "cr…

Spring AI Alibaba 人工介入实战｜Human-in-the-Loop 让 AI 更可靠
type: item
date: 2026-03-16T14:29:00+08:00
description: 引言在构建AI智能体应用时，我们经常面临一个关键挑战：如何让AI在执行某些敏感操作前获得人工确认？Spring AI Alibaba框架提供了强大的人工介入（Human-in-the-Loop）机制，让开发者能够精确控制AI工具的执行流程，在关键节点引入人工审批环节。本文将通过一个完整的实战示例，详细介绍如何在Spring AI Alibaba应用中实现人工介入功能。什么是人工介入？人工介入是一种机制，它允许AI智能体在执行特定工具前暂停执行，等待人工审批后再继续。这种机制特别适用于：敏感操作：如数据删除、资金转账等内容生成：如文章发布、诗歌创作等需要质量把控的场景权限控制：某些需要特定权限才能执行的操作审计要求：需要记录人工决策过程的场景实战示例：诗歌创作的人工审批让我们通过一个具体的例子来理解人工介入Hook的使用。这个示例展示了如何让AI在创作诗歌前获得人工确认。1. 项目依赖配置首先，确保你的项目中包含了Spring AI Alibaba相关依赖：<dependencies> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-agent-framework</artifactId> <version>1.1.2.0</version> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> <version>1.1.2.0</version> </dependency> </dependencies>2. 代码实现解析步骤1：构建AI模型// 构建DashScope API对象 DashScopeApi dashScopeApi = DashScopeApi.builder() .apiKey(System.getenv("AliQwen_API")) .build(); // 创建聊天模型 ChatModel chatModel = DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) .build();步骤2：配置工具public class PoetTool implements BiFunction<String, ToolContext, String> { public int count = 0; public PoetTool() { } @Override public String apply( @ToolParam(description = "The original user query that triggered this tool call") String originalUserQuery, ToolContext toolContext) { count++; System.out.println("Poet tool called : " + originalUserQuery); return "在城市的缝隙里， \n" + "一束光悄悄发芽， \n" + "穿过钢筋水泥的沉默， \n" + "在风中轻轻说话。 \n" + "\n" + "夜色如墨，却不再黑， \n" + "星星点亮了每一个角落， \n" + "我站在时间的边缘， \n" + "等一朵云，轻轻落下"; } public static ToolCallback createPoetToolCallback() { return FunctionToolCallback.builder("poem", new PoetTool()) .description("用来写诗的工具") .inputType(String.class) .build(); } public static ToolCallback createPoetToolCallback(String name, PoetTool poetTool) { return FunctionToolCallback.builder(name, poetTool) .description("用来写诗的工具") .inputType(String.class) .build(); } }步骤3：构建带有Hook的智能体// 这里我们配置了poem工具需要人工审批，并提供了审批时的描述信息。 Map<String, ToolConfig> approvalOn = Map.of( "poem"…

使用Spring AI Alibaba构建智能体Agent
type: item
date: 2026-03-13T15:58:00+08:00
description: 前言随着大语言模型(LLM)技术的快速发展，构建智能Agent应用变得越来越简单。本文将通过两个实际的代码示例，展示如何使用Spring AI Alibaba框架构建功能丰富的天气查询Agent，从基础的测试实现到生产级的完整应用。技术栈概述Spring AI Alibaba: 阿里巴巴开源的AI应用开发框架DashScope: 阿里云的AI模型服务平台React Agent: 基于ReAct(Reasoning and Acting)范式的智能代理示例一：SimpleTest - 快速入门添加核心依赖<dependencies> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-agent-framework</artifactId> <version>1.1.2.0</version> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> <version>1.1.2.0</version> </dependency> </dependencies>代码结构分析@Test void agentTest() throws GraphRunnerException { // 1. 初始化 DashScope API DashScopeApi dashScopeApi = DashScopeApi.builder() .apiKey(System.getenv("AliQwen_API")) .build(); // 2. 创建 ChatModel ChatModel chatModel = DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) .build(); // 3. 定义天气工具 ToolCallback weatherTool = FunctionToolCallback.builder("get_weather", new WeatherTool()) .description("获取某个城市的天气") .inputType(String.class) .build(); // 4. 构建React Agent ReactAgent agent = ReactAgent.builder() .name("weather_agent") .model(chatModel) .tools(weatherTool) .systemPrompt("你是一个非常有帮助的助手") .saver(new MemorySaver()) .build(); // 5. 调用Agent AssistantMessage response = agent.call("上海今天天气怎么样？"); System.out.println(response.getText()); }核心特性简洁的配置: 通过Builder模式快速构建Agent工具集成: 使用FunctionToolCallback将自定义函数包装为Agent可调用的工具内存存储: 使用MemorySaver保存对话历史中文支持: 完整的中文提示词和工具描述自定义工具实现class WeatherTool implements BiFunction<String, ToolContext, String> { @Override public String apply(String city, ToolContext toolContext) { return city + "今天天气非常好！"; } }这个简单的工具类展示了如何将业务逻辑封装为Agent可调用的函数。示例二：RealAgent - 真实的智能体高级特性概览相比SimpleTest，RealAgent展示了更多生产级特性：精细的模型配置多工具协同结构化输出对话上下文管理核心代码解析1. 系统提示词设计String SYSTEM_PROMPT = """ 你是一位擅长说**天气冷笑话/谐音梗**的专业天气预报员。 你可以使用两个工具： - **get_weather_for_location**：用于获取指定地点的天气 - **get_user_location**：用于获取用户当前所在位置 如果用户询问天气，**必须先确认地点**。 如果从问题中能判断出他们指的是**自己所在的地方**， 就使用 **get_user_location** …

详细解析Spring如何解决循环依赖问题
type: item
date: 2026-03-03T09:26:00+08:00
description: 在日常的Spring开发中，循环依赖是一个高频出现的问题，也是面试中的核心考点。本文将从概念定义、问题表现、核心原理到源码层面，全方位解析Spring是如何通过三级缓存机制优雅地解决单例Bean的循环依赖问题。一、什么是循环依赖？循环依赖，指的是两个或多个Bean之间互相持有对方的引用，形成闭环依赖关系。最典型的场景是Bean A依赖Bean B，同时Bean B又依赖Bean A。代码示例：@Component class A { // A依赖B @Resource private B b; } @Component class B { // B依赖A，形成循环 @Resource private A a; }在默认情况下，如果Spring不做特殊处理，项目启动时会抛出BeanCurrentlyInCreationException异常，提示存在循环依赖无法解决：二、Spring解决循环依赖的核心：三级缓存为了解决单例Bean的循环依赖问题，Spring设计了三级缓存机制，通过提前暴露半成品Bean的方式打破依赖闭环。三级缓存的定义缓存级别缓存名称作用一级缓存singletonObjects存放完全初始化完成的单例Bean（成品对象），供业务直接使用二级缓存earlySingletonObjects存放提前暴露的半成品Bean（已实例化但未完成属性填充和初始化）三级缓存singletonFactories存放ObjectFactory（对象工厂），这是一个函数式接口，仅在调用getObject()时才会创建Bean实例三、核心源码解析（基于 Spring 5.3.x）Spring处理Bean创建和循环依赖的核心逻辑集中在DefaultSingletonBeanRegistry类中，以下是关键源码及解析：public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { // 一级缓存：存放完全初始化好的单例Bean (成品) private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 三级缓存：存放Bean的工厂对象，用于创建提前暴露的Bean (半成品工厂) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 二级缓存：存放提前暴露的Bean实例 (半成品，未完成属性填充和初始化) private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 记录当前正在创建的Bean名称，解决循环依赖的关键判断 private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); /** * 核心方法：获取单例Bean（解决循环依赖的入口） * @param beanName Bean名称 * @param allowEarlyReference 是否允许提前引用半成品Bean * @return 单例Bean实例 */ @Nullable public Object getSingleton(String beanName, boolean allowEarlyReference) { // 第一步：优先从一级缓存获取成品Bean Object singletonObject = this.singletonObjects.get(beanName); // 如果一级缓存没有，且当前Bean正在创建中（循环依赖的核心判断条件） if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 第二步：从二级缓存获取提前暴露的半成品Bean singletonObject = this.earlySingletonObjects.get(beanName); // 如果二级缓存也没有，且允许提前引用 if (singletonObject == null && allowEarlyReference) { // 加锁保证并发安全 synchronized (this.singletonObjects) { // 双重检查（防止多…

Jakarta Validation 优雅实现参数校验：从基础使用到自定义扩展
type: item
date: 2026-02-03T16:38:20+08:00
description: 在后端开发中，参数校验是保障接口安全性和数据合法性的核心环节，硬编码的if-else校验逻辑不仅繁琐冗余，还会让代码可读性大打折扣。Jakarta Validation（原Java Validation）为我们提供了一套轻量、优雅的注解式参数校验方案，通过标准化的注解即可实现各类参数校验规则，大幅简化开发流程。本文将从基础注解使用、实战场景落地、统一异常处理到自定义校验注解，全方位讲解Jakarta Validation的使用技巧，结合实际业务代码示例，让你快速上手并灵活运用到项目中。一、核心校验注解速查Jakarta Validation提供了一系列开箱即用的校验注解，覆盖空值、长度、格式、数值、日期等绝大多数日常校验场景，核心注解及功能如下表所示，可直接作为开发速查手册：注解适用类型核心功能@NotNull所有类型字段值不能为null@NotBlank字符串不能为null，且去除首尾空格后长度大于0@NotEmpty字符串/集合/数组不能为null，且长度/元素个数大于0@Size(min, max)字符串/集合/数组长度/元素个数在[min, max]范围内@Pattern(regexp)字符串必须匹配指定的正则表达式@Email字符串必须符合合法的邮箱格式（支持自定义正则）@Min(value)数值类型数值必须大于等于value@Max(value)数值类型数值必须小于等于value@Positive数值类型必须为正数（大于0）@Negative数值类型必须为负数（小于0）@PositiveOrZero数值类型必须为正数或0@NegativeOrZero数值类型必须为负数或0@Future日期/时间类型必须是未来的时间@FutureOrPresent日期/时间类型必须是未来或当前时间@Past日期/时间类型必须是过去的时间@PastOrPresent日期/时间类型必须是过去或当前时间注解使用小技巧@NotBlank/@NotEmpty/@NotNull 区分：字符串优先用@NotBlank（过滤空白字符），集合/数组用@NotEmpty，非字符串非集合类型用@NotNull；注解组合使用：实际业务中可组合多个注解，如用户账号需同时满足「非空、正则匹配、长度限制」；默认提示语自定义：所有注解都支持message属性，用于自定义校验失败的提示信息，贴合业务场景。二、实战场景落地：三种核心使用方式Jakarta Validation的注解可根据参数传递方式灵活使用，核心分为「简单参数直接注解」「实体对象属性注解」两种核心场景，后者是项目中最常用的方式。场景1：GET请求简单参数，直接注解参数对于GET请求的URL拼接参数（如/user/get?name=test&id=1），可直接在接口方法的参数前添加校验注解，适用于参数数量少的简单场景。/** * 根据用户名和ID查询用户 * @param name 用户名（不能为空） * @param id 用户ID（必须为正数） * @return 用户信息 */ @GetMapping("/get") public CommonResult<UserVO> getUser( @NotBlank(message = "用户名不能为空") String name, @Positive(message = "用户ID必须为正数") Long id ) { UserVO user = userService.getByNameAndId(name, id); return CommonResult.success(user); }场景2：POST请求实体参数，注解+@Valid 触发校验对于POST/PUT请求，参数通常封装为实体对象（如新增/编辑用户的入参），只需在实体的属性上添加校验注解，并在接口方法的实体参数前添加@Valid（或@Validated）注解，即可触发整体校验逻辑。这是项目中最常用的方式，适合复杂参数的校验场景。步骤1：实体类添加校验注解@Data @Schema(description = "新增用户请求参数") public class UserSaveReqVO { @Schema(description = "用户编号（编辑时传，新增时不传）", example = "1024") private Long id; @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") @NotBlank(message = "用户账号不能为空") @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "用户账…

Spring Retry 重试机制：优雅解决接口调用失败问题
type: item
date: 2026-01-16T17:04:00+08:00
description: 在日常开发中，我们经常会遇到第三方接口不稳定、网络抖动导致的调用失败场景。很多人第一反应是在 try-catch 里写 for 循环重试，再搭配 Thread.sleep() 控制间隔——这种写法不仅冗余，还难以维护。今天给大家推荐 Spring Retry 框架，它基于 AOP 实现，能让你零侵入式地为方法添加重试功能，大幅简化代码！一、快速上手：三步集成 Spring Retry1. 添加 Maven 依赖Spring Retry 核心依赖 + AOP 依赖（因为其底层是 AOP 实现），这里推荐使用 2.0.12 稳定版本： <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>2.0.12</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>2. 启用 Spring Retry 功能在 Spring Boot 主启动类上添加 @EnableRetry 注解，一键开启重试功能：import org.springframework.retry.annotation.EnableRetry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @EnableRetry // 启用重试功能 @SpringBootApplication public class SpringRetryDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringRetryDemoApplication.class, args); } }3. 核心注解：@Retryable 标记重试方法在需要重试的方法上添加 @Retryable 注解，即可实现重试逻辑。基础用法import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; @Service public class RetryDemoService { // 标记该方法需要重试 @Retryable public void basicRetry() { int random = (int) (Math.random() * 10); System.out.println("当前随机数：" + random); // 模拟异常：随机数为偶数时抛出异常 if (random % 2 == 0) { throw new RuntimeException("随机数为偶数，触发异常"); } System.out.println("方法执行成功！"); } }基础用法说明未指定异常类型时，方法抛出任何异常都会触发重试。默认重试次数：3次（包含首次执行，实际重试 2 次）。默认重试间隔：1秒。当重试次数耗尽仍失败时，会抛出 ExhaustedRetryException 异常。二、进阶配置：灵活定制重试策略@Retryable 注解提供了丰富的属性，可根据业务需求精准控制重试逻辑。1. @Retryable 核心属性说明属性名作用示例value/retryFor指定触发重试的异常类型retryFor = RuntimeException.classinclude同 value，优先级更高include = {NullPointerException.class}exclude指定不触发重试的异常类型exclude = IllegalArgumentException.classmaxAttempts最大重试次数（包含首次执行）maxAttempts = 5backoff配置重试间隔策略@Backoff(delay = 1000, multiplier = 2)stateful是否有状态重试（异常信息保留）stateful = true2. 实战示例：指数退避重试需求：调用第三方接口时，仅在抛出 RuntimeException 时重试，最大重试 5 次，重试间隔按 1s → 2s → 4s …