1. Spring AI Advisor局部作用域解析
在Spring AI框架中,Advisor的设计遵循了AOP(面向切面编程)的思想,但它的作用范围可以通过配置实现精细化控制。从示例中可以看到,timerAdvisor仅在call2方法中生效,而在call1中不起作用,这种局部作用域的实现主要基于以下几个机制:
Advisor的Order属性:每个Advisor通过getOrder()方法返回的数值决定了其执行优先级。数值越小优先级越高,当多个Advisor存在时,这个属性会影响拦截链的构建顺序。
条件匹配规则:Spring AI内部使用Pointcut(切点)机制来匹配目标方法。可以通过自定义Pointcut实现精确的方法匹配,例如:
@Bean public Advisor timerAdvisor() { AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression("execution(* com.example.Service.call2(..))"); return new DefaultPointcutAdvisor(pointcut, new TimerInterceptor()); }- 上下文参数传递:如示例所示,通过chatClientRequest.context()可以获取调用上下文参数,这些参数可以动态控制Advisor是否生效。
实际开发中常见的坑:如果发现Advisor未按预期生效,首先检查getOrder()返回值是否被其他Advisor覆盖,其次验证Pointcut表达式是否能正确匹配目标方法。
2. 全局参数传递机制详解
2.1 上下文参数传递实现
示例中的ThirdAdvisor展示了如何获取全局传递的参数:
Object a = chatClientRequest.context().get("A");这种参数传递机制的核心实现原理包括:
- Context参数存储结构:底层使用ThreadLocal维护的Map结构,保证请求级别的参数隔离
- 参数生命周期:从ChatClient构建开始,贯穿整个请求处理链
- 类型安全建议:推荐使用类型明确的参数存取方式:
chatClientRequest.context().put("timeout", Duration.ofSeconds(30)); Duration timeout = chatClientRequest.context().get("timeout", Duration.class);2.2 参数传递的最佳实践
- 命名规范:采用全大写下划线命名(如USER_ID)避免键名冲突
- 性能考量:复杂对象建议先序列化为JSON字符串
- 安全注意:敏感参数应当加密存储,例如:
String encrypted = AESUtil.encrypt(paramValue); chatClientRequest.context().put("secure_param", encrypted);3. 基于Redis的多轮会话完整实现
3.1 技术架构设计
相比内存和JDBC方案,Redis实现的优势在于:
- 分布式会话支持
- TTL自动过期
- 高性能读写
- 持久化保障
整体架构流程:
sequenceDiagram participant Client participant Controller participant Advisor participant Redis participant AI Model Client->>Controller: 提问+会话ID Controller->>Advisor: 封装请求 Advisor->>Redis: 获取历史消息 Redis-->>Advisor: 返回消息列表 Advisor->>AI Model: 提交增强后的Prompt AI Model-->>Advisor: 返回响应 Advisor->>Redis: 存储新消息 Advisor-->>Controller: 返回流式响应 Controller-->>Client: 逐步输出3.2 核心组件实现细节
3.2.1 可序列化消息包装
Message接口的序列化问题需要特别注意:
public class SerializableMessage implements Serializable { // 必须显式声明serialVersionUID private static final long serialVersionUID = 1L; // 处理MessageType的序列化特殊情况 private String messageType; public SerializableMessage(Message message) { this.messageType = message.getMessageType().name(); // ...其他字段 } public Message getMessage() { return switch(MessageType.valueOf(messageType)) { case USER -> new UserMessage(content); case ASSISTANT -> new AssistantMessage(content); // ...其他类型处理 }; } }3.2.2 Redis内存管理策略
Key设计规范:
- 使用业务前缀(如"AI_CHAT:")
- 包含会话ID和用户ID(防越权)
- 示例:
AI_CHAT:{userId}:{sessionId}
过期策略配置:
spring: data: redis: # 默认30分钟过期 timeout: 1800- 内存优化技巧:
- 使用Hash结构存储消息
- 压缩长文本内容
- 限制历史消息条数(如最近20条)
3.3 完整工作流程示例
- 初始化请求:
// 前端传递会话ID String chatId = UUID.randomUUID().toString(); // 首次请求建立会话 Flux<String> response = chatClient.prompt() .advisors(advisor -> advisor.param("id", chatId)) .user("如何学习Spring AI?") .stream() .content();- 续聊实现:
// 使用相同chatId继续对话 Flux<String> followup = chatClient.prompt() .advisors(advisor -> advisor.param("id", existingChatId)) .user("还有更详细的学习路径吗?") .stream() .content();- 会话清除:
@DeleteMapping("/session/{chatId}") public void clearSession(@PathVariable String chatId) { redisTemplate.delete("REDIS_MESSAGE_KEY" + chatId); }4. 生产环境注意事项
4.1 性能优化方案
- Pipeline批量操作:
List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> { for (Message message : messages) { connection.stringCommands().set( ("MSG_" + chatId).getBytes(), serialize(message) ); } return null; });- 二级缓存策略:
@Cacheable(value = "chatMessages", key = "#chatId") public List<Message> getMessages(String chatId) { // Redis查询实现 }4.2 异常处理机制
- Redis故障降级:
try { return redisChatMemory.get(chatId); } catch (RedisConnectionFailureException e) { log.warn("Redis unavailable, falling back to local cache"); return localCache.get(chatId); }- 消息压缩容错:
public String compressMessage(Message message) { try { return Snappy.compress(message.getText()); } catch (IOException e) { // 压缩失败返回原文 return message.getText(); } }4.3 安全防护措施
- 会话隔离:
// 在获取会话前验证权限 if (!securityService.canAccessChat(userId, chatId)) { throw new AccessDeniedException(); }- 敏感词过滤:
public Message filterSensitive(Message message) { String filtered = sensitiveWordFilter.filter(message.getText()); return new UserMessage(filtered, message.getMetadata()); }5. 扩展应用场景
5.1 多模态会话支持
扩展SerializableMessage支持图片消息:
public class SerializableMessage { private MessageType type; private String textContent; private byte[] imageData; // 新增多媒体类型处理 public Message getMessage() { if (type == MessageType.IMAGE) { return new ImageMessage(imageData, metadata); } // ...原有逻辑 } }5.2 会话分析功能
基于Redis的会话分析实现:
public ChatStatistics analyzeChat(String chatId) { String json = redisTemplate.opsForValue().get(prefix + chatId); List<SerializableMessage> messages = parseJson(json); return new ChatStatistics( messages.stream().filter(m -> m.getType() == USER).count(), messages.stream().mapToInt(m -> m.getText().length()).average(), // ...其他统计指标 ); }5.3 分布式部署方案
跨中心同步配置:
@Bean public RedisTemplate<String, SerializableMessage> redisTemplate( RedisConnectionFactory connectionFactory) { RedisTemplate<String, SerializableMessage> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); template.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(SerializableMessage.class)); // 启用跨数据中心复制 template.setEnableTransactionSupport(true); return template; }通过这套基于Redis的Advisor实现,我们不仅解决了官方ChatMemory的局限性,还获得了分布式环境下的会话支持能力。在实际项目中,建议根据业务需求适当调整序列化策略和存储结构,比如对于高频访问的会话可以考虑引入本地缓存作为Redis的补充。