Java开发者AI转型第六课!Spring AI 灵魂架构 Advisor 切面拦截与自定义实战
大家好,我是直奔標杆!欢迎各位Java同行来到《Spring AI 零基础到实战》专栏的第六课,咱们继续并肩前行,一起攻克Spring AI的核心知识点~
在前五节课的学习中,咱们一步步让AI拥有了专属人设、实现了图片识别、完成了JSON映射,还搞定了打字机式的Flux流式输出。到这里,大家已经熟练掌握了单次AI调用的所有基础操作,相信不少小伙伴已经能上手简单的AI接口开发了。
但咱们做Java开发都清楚,实际的AI应用开发远比Demo复杂,工程中总会遇到各种棘手问题,分享给大家我实际开发中踩过的坑,也帮大家提前规避:
可观测性需求:每次调用AI前后,都需要打印完整日志,还要统计Token消耗,方便排查问题;
上下文记忆问题:大模型本身没有长期记忆,需要在每次请求前,自动拼接前10轮的聊天记录到提示词中;
RAG检索增强:遇到专业领域问题,得先从本地知识库检索相关文档,再把文档内容传给大模型,提升回答准确性。
难道要把这些重复的非业务逻辑,硬编码到每一个Controller接口里吗?相信大家和我一样,都觉得这种方式又繁琐又难维护!
好在Spring团队给出了极其优雅的解决方案——不搞反直觉的语法糖,回归咱们Java开发者最熟悉的底层哲学——AOP(面向切面编程)!今天这节课,咱们就一起深入拆解Spring AI的灵魂组件:ChatClient与Advisors,手把手教大家写出规范、可扩展的现代AI工作流,共同进步~
本节学习目标(建议收藏,对照学习)
1. 认知重塑:搞懂Spring AI为何抛弃复杂的管道语法,选择咱们熟悉的AOP模式,理解其设计初衷;
2. 源码解密:吃透Advisor的核心接口定义、执行顺序控制,以及请求/响应的载体结构,知其然也知其所以然;
3. 手写实战:一起动手写一个自定义的SimpleLoggerAdvisor,兼顾同步与流式调用的拦截,练熟实战技巧;
4. 官方武器库:全面梳理Spring AI内置的开箱即用顾问,掌握记忆、RAG、风控、重读提示等高频功能的使用方法。
Advisor 拦截器链的逻辑处理(核心重点)
在看源码之前,咱们先通过架构图直观感受下:当调用ChatClient的.call()方法时,请求是如何穿过层层Advisor拦截器,最终到达大模型的?(建议结合源码对照看,理解更透彻)
执行流程拆解(通俗易懂版)
当咱们执行.call()方法时,请求会被封装成ChatClientRequest对象,依次经过咱们配置的所有Advisor拦截器。每个Advisor都可以对请求进行修改(上一节课咱们讲的结构化输出,就是用ChatModelCallAdvisor拦截处理的,大家可以回头回顾下),之后请求会到达底层的ChatModel,获取到ChatClientResponse响应对象。紧接着,响应数据会原路返回,再次穿过所有Advisor拦截器,最终传递到咱们的业务代码中。
这里重点提醒大家:Advisor的执行采用经典的责任链模式,通过getOrder()方法控制执行顺序——值越小,请求阶段(Pre-process)越先执行,而响应阶段(Post-process)则越后执行,就像穿衣服和脱衣服的逻辑,很好记,大家多练两次就能掌握。
AOP思想在AI调用中的投射(新手必看)
Advisor(顾问)可以说是Spring AI的灵魂设计,核心目的就是解决AI交互中大量重复出现的通用逻辑(比如维护对话历史、检索文档、过滤敏感词等),实现非业务逻辑与业务逻辑的解耦,这也是咱们Java开发中最常用的设计思想之一。
如果大家熟悉Spring MVC的Interceptor、Servlet的Filter,或者Spring核心的@Around环绕通知,那么理解Advisor几乎没有门槛。我整理了一份对应关系表,方便大家快速对应记忆,一起吃透:
AOP概念 | Spring Web概念 | Spring AI概念 | 作用说明(通俗解读) |
|---|---|---|---|
Target (目标) | Controller逻辑 | ChatModel | 真正负责与OpenAI等大模型通信的底层客户端,是最终执行AI调用的核心 |
Proxy (代理) | DispatcherServlet | ChatClient | 咱们开发者直接调用的入口,负责组装请求、触发拦截链,简化调用流程 |
Advice (通知) | Filter/Interceptor | Advisor(核心) | 拦截请求与响应,将日志、记忆、检索等非业务逻辑抽离,实现解耦,便于维护 |
Join Point | 接口被调用的瞬间 | call()/stream() | 触发大模型调用的具体动作,也是Advisor拦截的核心节点 |
Advisor 核心接口解构(源码级解析)
想要自定义Advisor,首先得吃透它的基础骨架,这部分内容建议大家结合Spring AI源码一起看,我会尽量讲得通俗,避免晦涩,新手也能跟上。
2.1 核心接口规范(必掌握)
- Advisor:所有顾问的顶层基接口,继承了Spring的Ordered接口,必须实现getName()和getOrder()两个方法;
- getOrder():重中之重!决定Advisor的执行顺序,值越小,请求阶段越先执行,响应阶段越后执行,大家一定要注意规划顺序;
- CallAdvisor:专门用于拦截.call()同步请求,核心方法是adviseCall(),处理同步调用的拦截逻辑;
- StreamAdvisor:专门用于拦截.stream()流式请求,核心方法是adviseStream(),处理流式调用的拦截逻辑。
2.2 数据传输载体 (DTO)(高频使用)
- ChatClientRequest:请求载体,包含即将发给大模型的Prompt、系统指令、参数配置等,咱们可以根据需求自由修改;
- ChatClientResponse:响应载体,包含大模型返回的原始响应、文本内容、Token消耗等元数据,方便咱们获取调用详情;
- AdvisorContext:本质是一个Map,用于在不同Advisor之间共享数据(比如传递对话ID、用户信息等),非常实用。
实战案例:手写日志拦截器(新手可直接复用)
相信大家在开发中都遇到过这样的问题:AI回答不符合预期,排查半天发现是传给大模型的Prompt拼接错了。为了方便调试,今天咱们就一起手写一个日志拦截器,在请求发出前打印请求参数,响应返回后打印完整响应,大家可以直接复制到项目中使用。
这里分享一个最佳实践(亲测有效):如果业务允许,Advisor建议同时实现CallAdvisor和StreamAdvisor,确保同步和流式两种调用模式下都能正常工作,避免出现兼容问题。
下面开始动手,创建SimpleLoggerAdvisor类,代码如下(每一行都加了注释,大家看不懂的地方可以留言交流):
import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClientMessageAggregator; import org.springframework.ai.chat.client.ChatClientRequest; import org.springframework.ai.chat.client.ChatClientResponse; import org.springframework.ai.chat.client.advisor.api.CallAdvisor; import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain; import org.springframework.ai.chat.client.advisor.api.StreamAdvisor; import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain; import reactor.core.publisher.Flux; /** * 自定义日志拦截器(顾问) * 直奔標杆 实战案例,可直接复用 */ @Slf4j public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor { @Override public String getName() { // 返回当前类名作为顾问名称,便于排查问题 return this.getClass().getSimpleName(); } /** * 顺序控制:设为 0 表示优先级很高,最外层执行 * 大家可根据实际业务调整顺序 */ @Override public int getOrder() { return 0; } // ================= 拦截同步调用 (.call) ================= @Override public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) { // 请求大模型前:打印请求头和 Prompt,便于调试 logRequest(request); // 放行请求,交给链条中的下一个 Advisor 或底层 ChatModel ChatClientResponse response = chain.nextCall(request); // 拿到大模型结果后:打印返回信息,确认响应是否符合预期 logResponse(response); return response; } // ================= 拦截流式调用 (.stream) ================= @Override public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) { // [Pre-process] 请求发出前打印,和同步调用保持一致的日志逻辑 logRequest(request); // 放行流式请求,继续执行拦截链 Flux<ChatClientResponse> responseFlux = chain.nextStream(request); // 重点注意:流式响应是分段返回的,需要拼接完整后再打印 // 这里使用ChatClientMessageAggregator拼接所有响应片段 return new ChatClientMessageAggregator() .aggregateChatClientResponse(responseFlux, this::logResponse); } // 内部打印方法,封装日志逻辑,便于复用和修改 private void logRequest(ChatClientRequest request) { log.info("[发往大模型的请求]: {}", request); } private void logResponse(ChatClientResponse response) { log.info(" [大模型的完整响应]: {}", response); } }实战补充:如何将自定义Advisor挂载到ChatClient?
很多新手写完Advisor后,不知道怎么挂载使用,其实非常简单,只需在ChatClient构建时,通过.advisors()方法挂载即可,我给大家写了一个测试案例,直接复制就能运行:
@Test void testOllamaClient() { String result = chatClient.prompt() .user("你好, 请介绍一下 Spring AI") // 挂载咱们刚刚手写的日志顾问,直接new即可 .advisors(new SimpleLoggerAdvisor()) .call() .content(); System.out.println("最终结果:" + result); }运行测试后,控制台就能看到拦截器打印的完整请求报文和响应体了,大家可以亲自测试一下,加深理解。这里贴一下控制台的大致输出(供大家参考):
[发往大模型的请求]: ChatClientRequest[prompt=Prompt{messages=[UserMessage{content='你好, 请介绍一下 Spring AI'....
[大模型的完整响应]: ChatClientResponse[..., textContent=Spring AI 是一个由 Spring 官方团队推出的项目,旨在将人工智能能力....
Spring AI 内置 Advisors(高频实用,建议收藏)
吃透了Advisor的原理,咱们不仅能自己写拦截器,还能直接复用Spring团队为咱们预置的强大内置顾问,日常业务中90%的高级AI需求,都能直接开箱即用,节省大量开发时间,这也是Spring AI的优势所在——遵循Spring风格,统一抽象、易于集成,让咱们用写Spring Boot应用的习惯开发AI功能。
1. 聊天记忆顾问 (Memory):解决AI“失忆”问题
大模型天生没有长期记忆,这几个内置顾问能帮咱们轻松解决这个问题,分享给大家我常用的3个:
- MessageChatMemoryAdvisor:自动检索历史聊天记录,以Message对象列表的形式追加到请求中,适配大多数场景;
- PromptChatMemoryAdvisor:将历史聊天记录直接拼接成文本,放入System提示词中,配置简单,上手快;
- VectorStoreChatMemoryAdvisor:将会话历史存入向量数据库,对话时自动检索最相关的记忆片段,适合复杂对话场景。
2. 检索增强顾问 (RAG):企业级私有知识库核心
做企业级AI应用,RAG是必备功能,这两个内置顾问能帮咱们快速实现检索增强,不用自己从零开发:
- QuestionAnswerAdvisor:结合向量数据库,自动检索企业私有文档,追加到Prompt中,实现AI“外挂大脑”;
- RetrievalAugmentationAdvisor:更高级的模块化RAG流程控制组件,适合复杂的检索场景,扩展性更强。
3. 推理增强顾问 (Reasoning):提升AI回答准确率
- ReReadingAdvisor:基于著名的RE2论文实现,自动让大模型“重读”问题,能显著提升复杂逻辑题的回答准确率,亲测有效。
4. 内容安全顾问 (Safeguard):保障AI输出合规
- SafeGuardAdvisor:请求发送前、响应返回后,自动检测并过滤有害内容、敏感词,确保AI输出合规安全,避免踩坑。
Advisor开发最佳实践(避坑指南,亲测总结)
结合我实际开发中的经验,给大家总结了4个Advisor开发的最佳实践,避免大家走弯路,一起写出高质量的代码:
1. 单一职责:每个Advisor只负责一件事(比如只做日志记录,或只处理对话记忆),不要在一个Advisor里写几千行代码,否则后期难以维护;
2. 上下文共享:多个Advisor之间需要传递数据(比如当前用户ID、对话ID),直接用ChatClientRequest.getAdviseContext(),它本质是一个跨拦截链的Map,非常方便;
3. 兼容双模式:强烈建议同时实现CallAdvisor和StreamAdvisor,确保拦截逻辑在同步和流式调用下都能生效,避免出现兼容问题;
4. 严格控制顺序:一定要认真规划getOrder()的返回值,确保数据流正确。比如:内容过滤的Advisor Order要尽量小(外层拦截敏感词),RAG的Advisor Order稍靠后(处理完最终Query再检索)。
总结(核心提炼,快速回顾)
其实Spring AI的Advisor设计,本质就是Spring AOP思想在AI领域的延伸,Spring团队用咱们Java开发者最熟悉的方式,完美解决了AI提示词工程的复杂度,这也是Spring AI的核心哲学——把AI能力拆成可组合的稳定抽象,再用Spring风格把它们工程化。
通过Advisor,咱们能将日志、记忆、RAG检索、内容过滤等非业务逻辑彻底解耦,不仅降低了Java开发者的AI转型学习成本,还让代码拥有极强的扩展性。这里给大家一个形象的比喻:ChatClient就像一块精密的主板,而所有的Advisor都是即插即用的显卡和内存条,按需组合就能实现各种复杂的AI功能!
希望大家学完这节课,能动手实践起来,只有多写代码、多踩坑,才能真正掌握Advisor的使用,咱们一起在AI转型的路上稳步前行,直奔標杆!
下节预告(精彩不容错过)
理论武器已经装配完毕,接下来就是真刀真枪的实战环节!
咱们在实际调用AI API时,都会遇到一个头疼的问题——AI天生就是“失忆症患者”,根本记不住上一秒说的话!
下一节,第7课《AI失忆症克星!ChatMemory 对话历史管理与上下文实战》,咱们将深入剖析大模型失忆的本质原因,结合本节课学到的MessageChatMemoryAdvisor,搭配各种内存与分布式存储方案,手把手教大家打造一个拥有“长久记忆”的完美AI客服,实用性拉满!
精彩继续,咱们下节见~
往期内容(连贯学习,循序渐进)
Java开发者AI转型第五课!让AI懂规矩!Spring AI 结构化输出 (DTO) 映射与 Flux 流式打字机极速响应
关注我(直奔標杆),持续更新Spring AI零基础到实战系列,和各位Java同行一起,稳步实现AI转型,少走弯路、直奔目标!如有疑问,欢迎在评论区留言交流,一起探讨、共同进步~
