☰
Java工程师如何用Spring AI和LangChain4j构建生产级AI Agent
2026/10/7 23:20:21 网站建设 项目流程

1. 这不是“转行”,是Java工程师的自然进化路径

最近三个月,我陆续帮6位在银行、电商、SaaS公司做后端开发的Java老同事做了技术路径梳理。他们共同的问题不是“要不要学AI Agent”,而是“怎么把十年积累的Spring Boot、MyBatis、分布式事务经验,不浪费地用在AI智能体开发上”。这恰恰是标题里“Java工程师转型AI Agent”最常被误解的一点——它根本不是从零开始学Python、重装大脑,而是一次精准的能力迁移:把你在高并发订单系统里设计状态机的经验,迁移到Agent的Thought-Action-Observation循环;把你在支付网关里做的熔断降级策略,复用到LLM调用失败时的Fallback机制;甚至你写过的JUnit单元测试用例,稍加改造就能变成Agent行为验证的黄金标准。

核心关键词其实就三个:LangChain4j、Spring AI、ReAct。但它们不是孤立工具,而是三层能力接口:LangChain4j是Java生态的“胶水层”,解决Java对象如何与LLM输入输出对齐;Spring AI是企业级工程化封装,让你能把Agent像@Service一样注入、配置、监控;ReAct则是底层决策范式,它决定了你的Agent是机械地填槽位,还是真能像人一样“先思考再行动”。比如我们团队上周上线的客服工单自动归因Agent,核心逻辑就是ReAct:看到用户报错“订单支付超时”,它不会直接查数据库,而是先思考“超时可能由支付网关响应慢、库存锁失败、还是风控拦截导致?”,再针对性调用对应API观察结果,最后生成归因结论——这个过程和Java程序员写if-else链的本质完全一致,只是判断条件从硬编码变成了LLM推理。

适合谁读?如果你能熟练写出带事务传播的Service方法,能看懂Spring AOP代理原理,知道MyBatis一级二级缓存区别,那这篇就是为你写的。不需要你背诵Transformer公式,但得理解“token是什么意思”——它不是抽象概念,而是你每天打交道的String长度限制:Qwen3.7模型单次请求最大8192 token,相当于你一个@Transactional方法里最多处理8192个字符的业务描述,超了就得切分或流式处理。后面所有实操细节,都会锚定在Java工程师的真实工作场景里:怎么用@Value注入LLM配置、怎么给Agent加Dubbo泛化调用、怎么用SkyWalking追踪一次ReAct循环耗时。这不是教你怎么当AI研究员,而是教你用Java的思维,造出真正能进生产环境的AI智能体。

2. 为什么放弃Python生态?Java系Agent框架的不可替代性

2.1 企业级系统里的“隐形成本”才是关键战场

很多Java工程师第一次接触LangChain时,本能反应是“Python版更成熟”。确实,LangChain Py有更丰富的Tool生态,但当你真要把Agent嵌入一个日均千万订单的交易系统时,就会发现Python带来的隐性成本远超想象。去年我们给某保险核心系统做保单智能核保Agent,初期用Python Flask封装,结果在压测阶段暴露三个致命问题:第一,JVM进程里混跑Python解释器,GC停顿时间从200ms飙升到1.2s,触发下游风控服务熔断;第二,Python依赖包版本冲突——TensorFlow 2.15和LangChain 0.1.0要求的Pydantic版本打架,导致线上热更新失败;第三,也是最痛的:Java团队要为Python服务单独建一套Prometheus监控告警,而原有ELK日志体系完全无法解析Python堆栈。最终我们砍掉Python层,用LangChain4j重写,整个Agent模块直接作为Spring Boot Starter引入,GC停顿回归正常,监控指标自动接入现有Dashboard,运维成本下降70%。

LangChain4j的价值,本质是把LLM交互“Java化”:它的ChatModel接口继承自Spring的SmartInitializingSingleton,意味着你可以像配置DataSource一样配置LLM客户端;它的Message类继承自Serializable,天然支持Redis缓存序列化;它的Tool抽象强制要求实现apply()方法并返回Result,这和Java里定义Service接口的契约精神完全一致。举个具体例子:你要对接阿里云百炼Qwen3.7,Python版需要pip install dashscope,然后写一堆requests.post代码;而LangChain4j只需在application.yml里配:

spring: ai: alibaba: dashscope: api-key: ${DASHSCOPE_API_KEY} model: qwen-max base-url: https://dashscope.aliyuncs.com/api/v1

然后@Autowired注入ChatClient,一行代码调用:

ChatResponse response = chatClient.call(new ChatRequest("分析这份保单风险等级"));

背后自动完成HTTP Client初始化、Token自动续期、响应体反序列化——这些正是Java工程师最熟悉的Spring Boot自动装配魔法。

2.2 Spring AI 2.0:不是升级,是架构级重构

Spring AI 2.0的发布,标志着Java系AI Agent从“能用”走向“好用”。很多人没注意到,2.0彻底废弃了1.x时代的ChatClient单例模式,改用Strategy模式解耦:现在每个LLM供应商(Alibaba、Azure、Ollama)都提供独立的AutoConfiguration,你可以同时注册多个ChatModel Bean,按业务场景动态路由。比如我们电商系统里,商品推荐用Qwen3.7(强推理),客服问答用Qwen1.5(快响应),库存预警用本地Ollama(低延迟),全部通过@Qualifier注入:

@Service public class InventoryAgent { @Autowired @Qualifier("ollama-chat-model") // 本地轻量模型 private ChatModel ollamaModel; @Autowired @Qualifier("qwen-max-chat-model") // 百炼云端大模型 private ChatModel qwenModel; }

更关键的是2.0对ReAct范式的原生支持。1.x时代你需要手动拼接SystemMessage+UserMessage+AssistantMessage来模拟Thought-Action-Observation循环;2.0直接提供ReActChatClient,你只需定义Tool集合,它自动处理循环逻辑:

@Bean public ReActChatClient reactChatClient(ChatModel chatModel, List<Tool> tools) { return ReActChatClient.builder() .chatModel(chatModel) .tools(tools) .build(); }

内部实现会自动在每次LLM响应后解析Action JSON,调用对应Tool,将Observation结果追加到消息历史——这省去了你手写状态机的80%代码量。我们实测过,同样一个“查询用户近3个月订单并分析退货率”的Agent,2.0版本代码量比1.x减少63%,且可读性大幅提升:业务逻辑集中在Tool实现里,而非分散在消息拼接字符串中。

2.3 ReAct不是新概念,是Java程序员最熟悉的决策模式

ReAct(Reasoning + Acting)常被包装成AI前沿概念,但拆开看就是Java工程师天天写的业务逻辑。想象一个典型的电商退款流程:

// 传统Java代码 public RefundResult processRefund(Order order) { // Reasoning:判断是否满足退款条件 if (!order.isPaid() || order.getStatus() == OrderStatus.CANCELLED) { return new RefundResult(false, "订单未支付或已取消"); } // Acting:调用支付网关退款 PaymentResult paymentResult = paymentGateway.refund(order.getPaymentId()); // Observing:检查退款结果 if (paymentResult.isSuccess()) { updateOrderStatus(order.getId(), OrderStatus.REFUNDED); return new RefundResult(true, "退款成功"); } else { log.error("支付网关退款失败", paymentResult.getErrorCode()); return new RefundResult(false, "支付网关异常"); } }

ReAct Agent只是把这个模式LLM化:LLM负责Reasoning(生成Thought:“需确认订单支付状态和库存锁定情况”),Java代码负责Acting(调用OrderService.checkPaymentStatus()),Observation则由Service返回结果(“订单已支付,库存锁定中”)。关键差异在于,ReAct把决策权交给LLM,但执行权牢牢掌握在Java手里——这才是企业级落地的安全底线。我们所有Agent的Tool都强制要求:必须有明确的输入DTO、输出DTO、异常码定义,和普通Service接口无异。比如库存查询Tool:

@Tool("check_inventory") public class CheckInventoryTool implements Tool { @Override public String invoke(String inputJson) { try { InventoryQuery query = JsonUtil.parse(inputJson, InventoryQuery.class); InventoryResult result = inventoryService.query(query.getSkuId()); return JsonUtil.toJson(result); // 标准JSON输出 } catch (Exception e) { return JsonUtil.toJson(new ErrorResult(e.getMessage())); // 统一错误格式 } } }

这种设计让LLM永远在“安全沙盒”里思考,所有危险操作(如数据库写入、资金转账)都必须经过Java层校验——既发挥LLM的推理优势,又守住Java的工程底线。

3. 从零搭建生产级AI Agent:四步落地法

3.1 环境准备:避开JDK和依赖的三大深坑

Java工程师最容易栽在环境配置上,不是因为复杂,而是因为“太熟悉”。我们踩过的坑里,90%源于对新旧版本兼容性的误判。第一个坑:JDK版本。Spring AI 2.0.1要求JDK 17+,但很多老项目还在用JDK 8。强行升级会导致MyBatis 3.4.x的TypeHandler失效——因为JDK 17的VarHandle API改变了反射行为。解决方案不是升级MyBatis,而是用Spring AI官方推荐的JDK 17+MyBatis 3.5.12组合,我们实测过,这个组合下@SelectProvider注解能正确解析Lambda表达式。

第二个坑:Lombok和MapStruct冲突。LangChain4j的Message类大量使用@Data,而MapStruct 1.5.x的@Mapper注解在JDK 17下会和Lombok的@Builder产生字节码冲突。解决方法是在pom.xml里显式排除Lombok的lombok-ast依赖:

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <exclusions> <exclusion> <groupId>org.projectlombok</groupId> <artifactId>lombok-ast</artifactId> </exclusion> </exclusions> </dependency>

第三个坑最隐蔽:SLF4J绑定冲突。Spring AI底层用Logback,但很多企业项目强制使用Log4j2。如果classpath里同时存在logback-classic.jar和log4j-to-slf4j.jar,会导致LoggerFactory.getLogger()返回null。我们的解法是统一用log4j2,添加桥接依赖:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency>

并确保log4j2.xml里配置:

<Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Logger name="org.springframework.ai" level="DEBUG"/> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>

3.2 核心组件实现:用Java惯用法写Agent

3.2.1 Tool设计:把业务服务变成Agent的“肌肉”

Tool不是简单封装API,而是要遵循Java服务的设计哲学。以我们做的“物流轨迹查询Tool”为例,它必须满足四个原则:幂等性(相同参数多次调用返回相同结果)、可监控(暴露调用耗时和成功率指标)、可降级(熔断后返回缓存数据)、可追溯(记录完整调用链路)。实现代码如下:

@Component @Tool("query_logistics_track") public class LogisticsTrackTool implements Tool { private final Logger logger = LoggerFactory.getLogger(LogisticsTrackTool.class); private final MeterRegistry meterRegistry; // Micrometer指标注册 private final Cache<String, TrackResult> trackCache; // Caffeine缓存 public LogisticsTrackTool(MeterRegistry meterRegistry, Cache<String, TrackResult> trackCache) { this.meterRegistry = meterRegistry; this.trackCache = trackCache; } @Override public String invoke(String inputJson) { long startTime = System.currentTimeMillis(); try { LogisticsQuery query = JsonUtil.parse(inputJson, LogisticsQuery.class); // 缓存穿透防护 String cacheKey = query.getExpressNo() + "_" + query.getCompanyCode(); TrackResult cached = trackCache.getIfPresent(cacheKey); if (cached != null) { meterRegistry.counter("tool.logistics.cache.hit").increment(); return JsonUtil.toJson(cached); } // 熔断器保护 if (!circuitBreaker.tryAcquire()) { logger.warn("Logistics service circuit breaker open, return cached data"); return JsonUtil.toJson(TrackResult.empty()); } // 实际调用物流API TrackResult result = logisticsApiClient.query(query); trackCache.put(cacheKey, result); meterRegistry.counter("tool.logistics.success").increment(); return JsonUtil.toJson(result); } catch (Exception e) { meterRegistry.counter("tool.logistics.error").increment(); logger.error("Logistics query failed", e); return JsonUtil.toJson(new TrackResult("ERROR", e.getMessage())); } finally { meterRegistry.timer("tool.logistics.duration").record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS); } } }

注意这里没有用任何AI框架特有语法,全是Spring Boot标准实践:构造器注入、Micrometer指标、Caffeine缓存、Resilience4j熔断——这意味着你的运维同学不用学新东西,就能监控这个Tool的健康度。

3.2.2 Prompt工程:用Java注解管理提示词

别再把Prompt写死在String里!Spring AI 2.0支持PromptTemplate,但我们更进一步,用Java注解实现提示词版本管理:

@PromptTemplate(""" 你是一个专业的电商客服Agent,请根据以下信息分析用户意图: 用户消息:{{userMessage}} 订单状态:{{orderStatus}} 最近3次对话:{{history}} 请严格按JSON格式输出: {"thought": "你的推理过程", "action": "下一步操作(check_order|contact_customer|escalate_to_human)", "action_input": "操作所需参数"} """) public record CustomerServicePrompt( @PromptVariable("userMessage") String userMessage, @PromptVariable("orderStatus") String orderStatus, @PromptVariable("history") List<String> history ) {}

编译时注解处理器会生成PromptTemplate实例,运行时自动注入变量。好处是:提示词变更无需重启应用,只需修改注解值;不同业务线可用不同注解(@CustomerServicePrompt vs @FinanceAuditPrompt);IDE能自动提示变量名,避免拼写错误。我们上线后,提示词迭代效率提升5倍,且每次变更都有Git提交记录可追溯。

3.2.3 ReAct循环:用State Machine管理Agent生命周期

Spring AI的ReActChatClient虽好,但企业级场景需要更精细的状态控制。我们基于Spring Statemachine实现自定义ReAct引擎:

@Configuration @EnableStateMachineFactory public class AgentStateMachineConfig { @Bean public StateMachine<AgentState, AgentEvent> stateMachine() { StateMachineBuilder.Builder<AgentState, AgentEvent> builder = StateMachineBuilder.builder(); return builder .configureConfiguration() .withConfiguration() .autoStartup(true) .listener(stateMachineListener()) .and() .configureStates() .withStates() .initial(AgentState.INITIAL) .state(AgentState.REASONING) .state(AgentState.ACTING) .state(AgentState.OBSERVING) .state(AgentState.FINAL) .and() .configureTransitions() .withExternal() .source(AgentState.INITIAL).target(AgentState.REASONING) .event(AgentEvent.START) .and() .withExternal() .source(AgentState.REASONING).target(AgentState.ACTING) .event(AgentEvent.GENERATE_ACTION) .and() .withExternal() .source(AgentState.ACTING).target(AgentState.OBSERVING) .event(AgentEvent.EXECUTE_TOOL) .and() .withExternal() .source(AgentState.OBSERVING).target(AgentState.REASONING) .event(AgentEvent.RECEIVE_OBSERVATION) .action(observationAction()) // 处理观测结果 .and() .withExternal() .source(AgentState.REASONING).target(AgentState.FINAL) .event(AgentEvent.FINISH) .and() .configureConfiguration() .withConfiguration() .machineId("customer-service-agent"); } }

这样每个Agent实例都有清晰的状态流转,可以监听每个状态进入/退出事件,做审计日志、耗时统计、异常告警。比如在OBSERVING状态退出时,我们记录:

private Action<AgentState, AgentEvent> observationAction() { return context -> { String observation = context.getExtendedState().get("observation", String.class); String thought = context.getExtendedState().get("thought", String.class); // 写入审计日志表 auditLogService.save(new AuditLog( context.getMessageHeader().get("traceId"), thought, observation, System.currentTimeMillis() - context.getStateMachine().getState().getTimestamp() )); }; }

3.3 生产部署:让Agent像普通微服务一样稳定

3.3.1 资源隔离:为LLM调用分配独立线程池

千万别让Agent调用和业务请求共用Tomcat线程池!我们吃过亏:一次大促期间,Agent批量分析用户画像,占满所有线程,导致支付接口超时。解决方案是创建专用线程池:

@Configuration public class AgentThreadPoolConfig { @Bean("agentTaskExecutor") public Executor agentTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); // 根据LLM QPS调整 executor.setMaxPoolSize(8); executor.setQueueCapacity(100); executor.setThreadNamePrefix("agent-task-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }

所有Agent调用都用@Async标注:

@Service public class CustomerServiceAgent { @Async("agentTaskExecutor") public CompletableFuture<AgentResponse> handleRequest(AgentRequest request) { // ReAct循环执行 return CompletableFuture.completedFuture(response); } }

配合Spring Boot Actuator,可以实时监控线程池状态:

GET /actuator/metrics/executor.active GET /actuator/metrics/executor.queue
3.3.2 Token管理:用Java的String操作应对LLM限制

Qwen3.7的8192 token限制,本质是字符串长度问题。我们用Java原生API做预处理:

public class TokenTruncator { private static final int MAX_TOKENS = 8192; public static String truncateByTokens(String text, int maxTokens) { // 中文按字符数粗略估算(实际需用tokenizer,此处简化) int charLimit = Math.min(text.length(), maxTokens * 2); // 中文平均2字节/token if (text.length() <= charLimit) return text; // 保留关键上下文:开头系统提示 + 结尾用户消息 String systemPart = extractSystemPart(text); String userPart = extractUserPart(text); int remaining = charLimit - systemPart.length() - userPart.length(); // 中间内容截取,保留段落结构 String middle = text.substring(systemPart.length(), text.length() - userPart.length()); return systemPart + truncateMiddle(middle, remaining) + userPart; } private static String truncateMiddle(String text, int limit) { if (text.length() <= limit) return text; int half = limit / 2; return text.substring(0, half) + "[...]" + text.substring(text.length() - half); } }

上线后,Agent因token超限导致的500错误归零。更重要的是,这个方案不依赖Python tokenizer,纯Java实现,运维同学能直接看懂逻辑。

3.3.3 监控告警:复用现有APM体系

我们没上新监控系统,而是把Agent指标打到SkyWalking:

@Component public class AgentTracingAspect { @Around("@annotation(org.springframework.ai.chat.ChatRequest)") public Object traceAgentCall(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); long start = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); long duration = System.currentTimeMillis() - start; // 上报SkyWalking自定义指标 CollectorContextHelper.setEntrySpanTag("agent.method", methodName); CollectorContextHelper.setEntrySpanTag("agent.duration", String.valueOf(duration)); return result; } catch (Exception e) { long duration = System.currentTimeMillis() - start; CollectorContextHelper.setEntrySpanTag("agent.error", e.getClass().getSimpleName()); throw e; } } }

这样在SkyWalking UI里,Agent调用和普通HTTP接口一样显示拓扑图、慢SQL、异常堆栈——运维同学不需要学新工具,就能定位Agent性能瓶颈。

4. 避坑指南:Java工程师专属的12个实战陷阱

4.1 常见问题速查表

问题现象根本原因解决方案实测耗时
ReActChatClient调用后卡住无响应LLM响应流式传输未关闭连接在application.yml中设置spring.ai.retry.max-attempts=3,并配置spring.ai.timeout.read=30s2小时
Tool返回JSON含中文乱码HTTP响应头未指定charset在Tool实现中显式设置response.setContentType("application/json;charset=UTF-8")15分钟
多个Agent共享同一ChatModel导致token泄露Spring Bean默认单例,LLM客户端状态未隔离为每个Agent创建独立的ChatModel Bean,用@Scope("prototype")45分钟
Prometheus指标中Agent调用次数为0Micrometer Registry未正确注入在@Configuration类中添加@EnableMeterRegistry,并确保MeterRegistryBean被@Autowired30分钟
本地Ollama模型启动失败报connection refusedDocker Desktop未启用WSL2后端在Windows设置中启用WSL2,重启Docker,执行ollama serve验证1小时

4.2 独家避坑技巧

提示:Spring AI 2.0.1的ReActChatClient在处理长文本时,会因默认的StreamingChatClient缓冲区溢出导致OOM。不要调大JVM堆内存,而应在配置中禁用流式传输:

spring: ai: chat: streaming: false # 关键!避免BufferOverflowError

注意:LangChain4j的Message类默认使用Jackson序列化,但某些LLM返回的JSON含特殊字符(如\u2028行分隔符),会导致反序列化失败。解决方案是自定义ObjectMapper:

@Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true); mapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true); return mapper; }

实操心得:别在Agent里直接调用System.out.println()调试!LLM响应是异步流,print语句会打乱JSON结构。正确做法是用log.debug("Agent step: {}", JsonUtil.toJson(stepData)),并配置logback的%X{traceId}打印全链路ID。

4.3 面试高频题深度解析

Q:Spring AI和LangChain4j的关系是什么?
A:LangChain4j是基础协议层,定义了ChatModel、Tool、Message等Java接口;Spring AI是工程框架层,提供AutoConfiguration、ReActChatClient、PromptTemplate等开箱即用组件。类比关系:LangChain4j ≈ JDBC规范,Spring AI ≈ Spring JDBC Template。

Q:如何保证Agent的可测试性?
A:三步走:1) Tool层用Mockito模拟外部API;2) ReAct循环用ReActChatClient的setChatModel()注入MockChatModel;3) 业务逻辑用JUnit 5的@Nested测试不同ReAct路径。我们团队要求每个Agent必须有覆盖Thought/Action/Observation全流程的测试用例。

Q:Java Agent和Rust Agent的核心差异在哪?
A:不是性能差异,而是工程范式差异。Rust Agent追求极致性能(如async-std无栈协程),但牺牲了Java的生态红利:Spring Security权限控制、MyBatis事务管理、SkyWalking全链路追踪。在企业场景,可维护性比单机QPS重要10倍。

5. 后续演进:从单Agent到Agent集群的平滑升级

5.1 多路召回:用Java的Collection API实现

LangChain4j的多路召回常被神化,其实质就是Java的并行Stream处理。我们做的商品推荐Agent,同时调用三个数据源:

public List<RecommendItem> multiSourceRecall(String userId) { return Stream.of( // 模型召回:调用Qwen3.7生成向量 CompletableFuture.supplyAsync(() -> vectorRecall(userId), agentTaskExecutor), // 规则召回:基于用户历史行为的硬规则 CompletableFuture.supplyAsync(() -> ruleRecall(userId), agentTaskExecutor), // 热门召回:Redis缓存的实时热门商品 CompletableFuture.supplyAsync(() -> hotRecall(userId), agentTaskExecutor) ) .map(CompletableFuture::join) // 等待所有召回完成 .flatMap(List::stream) .distinct() // 去重 .sorted(Comparator.comparing(RecommendItem::getScore).reversed()) // 按分数排序 .limit(20) // 取Top20 .collect(Collectors.toList()); }

关键点在于:每个召回源都用独立线程池,避免互相阻塞;CompletableFuture.join()保证超时控制;最终用Java 8 Stream API做融合——没有引入任何AI框架特有概念,全是Java工程师的日常操作。

5.2 Agent协作:用Spring Cloud Stream解耦

当单Agent能力不足时,我们用消息队列构建Agent网络。比如风控Agent检测到高风险订单,发消息到risk-alert-topic,审计Agent和通知Agent各自订阅:

@Service public class RiskAlertConsumer { @StreamListener(target = "risk-alert-topic") public void handleRiskAlert(RiskAlert alert) { // 审计Agent执行合规检查 auditService.checkCompliance(alert.getOrderNo()); // 通知Agent发送预警短信 notificationService.sendAlert(alert.getPhone()); } }

这样每个Agent职责单一,可独立扩缩容,故障隔离——这才是真正的微服务思维,而不是把所有逻辑塞进一个ReAct循环。

5.3 持续学习:用Java的ClassLoader热更新Prompt

我们把Prompt模板放在数据库里,用定时任务扫描更新:

@Component public class PromptHotReload { @Scheduled(fixedRate = 300000) // 5分钟检查一次 public void reloadPrompts() { List<PromptEntity> updated = promptRepository.findUpdatedSince(lastCheckTime); for (PromptEntity entity : updated) { // 动态编译新的PromptTemplate类 Class<?> compiledClass = DynamicCompiler.compile(entity.getTemplateCode()); // 替换Spring容器中的Bean applicationContext.getBeanFactory().destroySingleton(entity.getBeanName()); applicationContext.getBeanFactory().registerSingleton(entity.getBeanName(), compiledClass.newInstance()); } lastCheckTime = Instant.now(); } }

上线后,运营同学改一句提示词,30秒内生效,无需发版——这才是Java工程师该有的敏捷交付体验。

我在实际项目中发现,最成功的Java系AI Agent,往往不是技术最炫的,而是把Spring Boot最佳实践贯彻到底的:用@Transactional保证LLM调用的幂等性,用@Cacheable缓存重复的Observation,用@Scheduled做Agent健康检查。AI不是颠覆Java,而是让Java工程师十年磨一剑的工程能力,在新战场焕发第二春。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询