SpringAIAlibaba之深度剖析序列化过程中LinkedHashMap类型转换异常(十)
引言
在使用 Spring AI Alibaba 构建对话系统时,你是否遇到过这样的错误?
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to
class org.springframework.ai.chat.messages.AbstractMessage
第一次调用正常,第二次调用就崩溃?明明序列化和反序列化用的是同一个 ObjectMapper,为什么还会出现类型转换问题?
本文将深入分析,带你一步步揭开这个"诡异"问题的真相。
一、问题现场
用户的配置
一位用户在使用 RedisSaver 进行对话历史持久化时,按照最佳实践配置了自定义的 ObjectMapper:
// 1. 创建自定义 ObjectMapper
@Configuration
public class RedisConfig {
@Bean
public ObjectMapper customObjectMapper() {
return Jackson2ObjectMapperBuilder.json()
.modules(new JavaTimeModule())
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
}
// 2. 使用自定义 ObjectMapper 配置 RedissonClient
@Bean
public RedissonClient redissonClient(ObjectMapper customObjectMapper) {
Config config = new Config();
config.setCodec(new JsonJacksonCodec(customObjectMapper));
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
// 3. 创建 RedisSaver
@Bean
public RedisSaver redisSaver(RedissonClient redissonClient,
ObjectMapper customObjectMapper) {
SpringAIJacksonStateSerializer serializer =
new SpringAIJacksonStateSerializer(OverAllState::new, customObjectMapper);
return RedisSaver.builder()
.redisson(redissonClient)
.stateSerializer(serializer)
.build();
}
}
二、问题复现
// 第一次调用 - 正常
ReactAgent agent = ReactAgent.builder()
.model(chatModel)
.saver(redisSaver)
.build();
RunnableConfig config = RunnableConfig.builder()
.threadId("user-conversation-001")
.build();
// ✅ 第一次成功
agent.stream(new UserMessage("1+1等于几?"), config).subscribe();
// ❌ 第二次失败!
agent.stream(new UserMessage("那2+2等于几?"), config).subscribe();
错误堆栈:
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to
class org.springframework.ai.chat.messages.AbstractMessage
at com.alibaba.cloud.ai.graph.agent.node.AgentLlmNode.lambda$buildNodeAction$0
(AgentLlmNode.java:149)
症状总结:
- ✅ 第一次调用正常
- ❌ 第二次调用抛出
ClassCastException - 🤔 错误信息:
LinkedHashMap无法转换为AbstractMessage
三、完整的代码链路追踪
要理解这个问题,我们需要追踪完整的序列化/反序列化链路。
第一次调用:保存到 Redis
┌─────────────────────────────────────────────────────────────────┐
│ 第一次调用:ReactAgent.stream("1+1等于几?") │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 1: 应用层处理 │
│ - AgentLlmNode 调用 LLM │
│ - 生成 AssistantMessage │
│ - state.data = { │
│ "messages": [UserMessage, AssistantMessage] │
│ } │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 2: 【序列化层 1】SpringAIJacksonStateSerializer │
│ 文件:JacksonStateSerializer.java:95-101 │
│ │
│ public void writeData(Map<String, Object> data, ObjectOutput out) {│
│ String json = objectMapper.writeValueAsString(data); │
│ Serializer.writeUTF(json, out); │
│ } │
│ │
│ 使用 customObjectMapper 序列化 │
│ ⚠️ 注意:虽然传入的是 customObjectMapper,但 Spring AI 在 │
│ 构造函数中给它配置了 enableDefaultTyping() │
└─────────────────────────────────────────────────────────────────┘
↓
生成的 JSON:
┌─────────────────────────────────────┐
│ { │
│ "messages": [ │
│ { │
│ "@class": "...UserMessage", │← 有类型信息!
│ "content": "1+1等于几?" │
│ }, │
│ { │
│ "@class": "...AssistantMessage",│
│ "content": "等于2" │
│ } │
│ ] │
│ } │
└─────────────────────────────────────┘
↓
⚠️ 但是注意!
数组本身没有 @class 字段(被过滤了)
只有数组元素有 @class 字段
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 3: 【序列化层 2】Java 对象序列化 │
│ 文件:RedisSaver.java:91-102 │
│ │
│ private String serializeCheckpoints(List<Checkpoint> checkpoints) {│
│ ObjectOutputStream oos = ...; │
│ checkpointSerializer.write(checkpoint, oos); │
│ return Base64.getEncoder().encodeToString(bytes); │
│ } │
│ │
│ 将 Checkpoint 对象(包含 JSON 字符串)序列化为字节数组 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 4: Base64 编码 │
│ 字节数组 → Base64 字符串 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 5: 存储到 Redis │
│ 文件:RedisSaver.java:289 │
│ │
│ RBucket<String> bucket = redisson.getBucket(key); │
│ bucket.set(base64String); // ← 只存储字符串! │
│ │
│ ⚠️ 关键:RBucket<String> 意味着 Redis 只做字符串存储 │
│ 不会使用 RedissonClient 的 JsonJacksonCodec! │
└─────────────────────────────────────────────────────────────────┘
↓
✅ 第一次调用成功
第二次调用:从 Redis 加载
┌─────────────────────────────────────────────────────────────────┐
│ 第二次调用:ReactAgent.stream("那2+2等于几?") │
│ 需要加载之前的对话历史 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 1: 从 Redis 读取 │
│ 文件:RedisSaver.java:200-201 │
│ │
│ RBucket<String> bucket = redisson.getBucket(key); │
│ String content = bucket.get(); // ← 只读取字符串 │
│ │
│ 返回:Base64 字符串 │
│ ✅ Redis 的工作结束,没有反序列化操作 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 2: Base64 解码 │
│ Base64 字符串 → 字节数组 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 3: 【反序列化层 1】Java 对象反序列化 │
│ 文件:RedisSaver.java:104-118 │
│ │
│ ObjectInputStream ois = new ObjectInputStream(bais); │
│ checkpoint = checkpointSerializer.read(ois); │
│ │
│ 返回:Checkpoint 对象(包含 JSON 字符串) │
│ ✅ 这一步正常 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 4: 【反序列化层 2】JSON 反序列化 │
│ 文件:JacksonStateSerializer.java:104-108 │
│ │
│ public Map<String, Object> readData(ObjectInput in) { │
│ String json = Serializer.readUTF(in); │
│ return objectMapper.readValue(json, │
│ new TypeReference<Map<String, Object>>() {}); │
│ } │
│ │
│ 🔥 问题发生在这里! │
└─────────────────────────────────────────────────────────────────┘
↓
Jackson 的反序列化决策过程:
┌─────────────────────────────────────┐
│ 输入 JSON: │
│ { │
│ "messages": [ │← 数组本身没有 @class
│ {"@class": "UserMessage", ...}, │← 元素有 @class
│ {"@class": "AssistantMessage", ...}│
│ ] │
│ } │
│ │
│ 目标类型:Map<String, Object> │
│ ↑ ↑ │
│ 键类型 值是 Object │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Jackson 的推理: │
│ │
│ 1. "messages" 的值是一个 JSON 数组 │
│ 2. 数组本身没有类型信息(@class) │
│ 3. 目标类型声明为 Object(太宽泛) │
│ 4. 虽然数组元素有 @class... │
│ 5. 但是!因为: │
│ - 父容器(数组)无类型信息 │
│ - 目标类型是 Object │
│ - Jackson 无法确定如何处理 │
│ │
│ 🔥 决定:使用 LinkedHashMap │
│ 表示 JSON 对象 │
└─────────────────────────────────────┘
↓
反序列化结果:
┌─────────────────────────────────────┐
│ Map<String, Object> state = { │
│ "messages": [ │
│ LinkedHashMap { │← ❌ 不是 UserMessage!
│ "content": "1+1等于几?" │
│ }, │
│ LinkedHashMap { │← ❌ 不是 AssistantMessage!
│ "content": "等于2" │
│ } │
│ ] │
│ } │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 5: 应用层使用数据 │
│ 文件:AgentLlmNode.java:149 │
│ │
│ if (state.value("messages").isEmpty()) { │
│ // ... │
│ } else { │
│ // 🔥 类型转换发生在这里 │
│ messages = (List<Message>) state.value("messages").get(); │
│ // ↑ ↑ │
│ // 期望的类型 实际是 ArrayList<LinkedHashMap> │
│ } │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 为什么强制转换能通过编译? │
│ │
│ Object obj = state.value("messages").get();│
│ // 实际类型:ArrayList<LinkedHashMap>│
│ │
│ List<Message> messages = (List<Message>) obj;│
│ // Java 泛型擦除:只检查 List │
│ // ✅ 编译通过!运行时也不会立即报错 │
│ │
│ Message firstMsg = messages.get(0); │
│ // 返回 LinkedHashMap │
│ // 尝试当作 Message 使用... │
│ │
│ 🔥 ClassCastException! │
└─────────────────────────────────────┘
四、问题根源深度分析
为什么同一个 ObjectMapper 会产生不同结果?
这是最让人困惑的地方:序列化和反序列化都用同一个 ObjectMapper,为什么还会出问题?
原因1:useForType() 过滤了集合类型
当用户传入 customObjectMapper 时,SpringAIJacksonStateSerializer 的构造函数会修改它:
// SpringAIJacksonStateSerializer.java:66-97
public SpringAIJacksonStateSerializer(
AgentStateFactory<OverAllState> stateFactory,
ObjectMapper objectMapper) {
super(stateFactory, objectMapper);
// 配置类型解析器
ObjectMapper.DefaultTypeResolverBuilder typeResolver =
new ObjectMapper.DefaultTypeResolverBuilder(...) {
@Override
public boolean useForType(JavaType t) {
// 🔥 关键:过滤掉集合类型!
if (t.isCollectionLikeType() ||
t.isTypeOrSubTypeOf(Collection.class)) {
return false; // ← List 不会有 @class 字段
}
// 过滤掉 Map 类型
if (t.isTypeOrSubTypeOf(Map.class) || t.isMapLikeType()) {
return false;
}
return super.useForType(t);
}
};
objectMapper.setDefaultTyping(typeResolver);
}
结果:
- ✅ 普通对象(如
UserMessage)会有@class字段 - ❌ 集合类型(如
List)不会有@class字段 - ❌ Map 类型不会有
@class字段
原因2:TypeReference<Map<String, Object>> 的影响
反序列化时使用的目标类型:
// JacksonStateSerializer.java:106
return objectMapper.readValue(json,
new TypeReference<Map<String, Object>>() {});
// ↑
// 值类型是 Object
Jackson 的推理过程:
- 看到 JSON 数组:
[{...}, {...}] - 数组本身没有
@class字段(被useForType过滤了) - 目标类型是
Object(太宽泛,可以是任何类型) - 虽然数组元素有
@class字段 - 但因为父容器无类型信息 + 目标类型宽泛
- Jackson 不确定如何处理
- 默认使用
LinkedHashMap表示 JSON 对象
原因3:序列化和反序列化的不对称性
序列化时:
List<Message> → JSON 数组
元素有 @class:[{"@class":"UserMessage",...}]
✅ 看起来正常
反序列化时:
JSON 数组 → TypeReference<Map<String, Object>>
目标类型是 Object
数组本身无类型信息
Jackson 无法推断出应该是 List<Message>
❌ 使用 LinkedHashMap 作为后备方案
这是用户的问题还是框架的问题?
🤔 争议点分析
观点A:用户配置不当(用户的问题)
理由:
- 用户不应该传入自定义的
ObjectMapper SpringAIJacksonStateSerializer已经提供了默认配置- 如果使用默认配置,问题不会发生
代码示例:
// ❌ 错误做法
SpringAIJacksonStateSerializer serializer =
new SpringAIJacksonStateSerializer(OverAllState::new, customObjectMapper);
// ✅ 正确做法
SpringAIJacksonStateSerializer serializer =
new SpringAIJacksonStateSerializer(OverAllState::new);
// 使用默认 ObjectMapper
观点B:框架设计缺陷(框架的问题)
理由:
- API 设计误导:既然提供了接受
ObjectMapper的构造函数,就应该正确工作 - 文档不足:没有明确说明传入自定义
ObjectMapper的风险 - useForType 过滤策略有问题:过滤掉集合类型导致信息丢失
- 反序列化策略脆弱:依赖
TypeReference<Map<String, Object>>过于宽泛
✅ 结论:这是一个框架设计问题
虽然用户可以通过不传入自定义 ObjectMapper 来避免,但本质上是框架的问题:
1. API 契约被违反
// 框架提供了这个构造函数
public SpringAIJacksonStateSerializer(
AgentStateFactory<OverAllState> stateFactory,
ObjectMapper objectMapper) {
// ...
}
用户合理期望:
- ✅ 传入的
ObjectMapper应该被正确使用 - ✅ 序列化和反序列化应该是对称的
- ✅ 不应该产生类型转换异常
实际情况:
- ❌ 传入的
ObjectMapper被框架修改 - ❌ 修改后的配置导致反序列化失败
- ❌ 没有任何警告或文档说明
2. useForType 的设计缺陷
为什么要过滤掉集合类型?框架开发者的考虑可能是:
- 避免 JSON 过于臃肿(集合的
@class信息很长) - 认为集合的元素有类型信息就够了
但这导致了:
- ❌ 反序列化时缺少关键信息
- ❌ Jackson 无法正确重建对象图
- ❌ 必须使用精确的
TypeReference
3. 缺少防御性编程
框架应该:
- 验证配置:检测用户传入的
ObjectMapper是否兼容 - 明确文档:说明哪些配置是必需的
- 提供诊断:在反序列化失败时给出有用的错误信息
- 提供辅助方法:让用户可以安全地自定义配置
五、解决方案
方案1:用户侧 - 不传入自定义 ObjectMapper(临时方案)
@Bean
public RedisSaver redisSaver(RedissonClient redissonClient) {
// ✅ 使用默认配置
SpringAIJacksonStateSerializer serializer =
new SpringAIJacksonStateSerializer(OverAllState::new);
return RedisSaver.builder()
.redisson(redissonClient)
.stateSerializer(serializer)
.build();
}
优点:
- ✅ 立即解决问题
- ✅ 不需要等待框架更新
缺点:
- ❌ 失去了自定义配置的能力
- ❌ 不能添加自定义的 Jackson 模块
方案2:用户侧 - 正确配置自定义 ObjectMapper(推荐)
如果确实需要自定义配置,可以这样做:
@Bean
public RedisSaver redisSaver(RedissonClient redissonClient) {
// 1. 先创建默认的 serializer
SpringAIJacksonStateSerializer serializer =
new SpringAIJacksonStateSerializer(OverAllState::new);
// 2. 获取它内部已经配置好的 ObjectMapper
ObjectMapper mapper = ((JacksonStateSerializer) serializer).objectMapper();
// 3. 在此基础上添加自定义模块
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return RedisSaver.builder()
.redisson(redissonClient)
.stateSerializer(serializer)
.build();
}
优点:
- ✅ 保留了类型信息处理
- ✅ 可以添加自定义配置
- ✅ 不会出现 LinkedHashMap 问题
方案3:框架侧 - 改进 useForType 策略
框架应该修改 useForType 的过滤逻辑:
@Override
public boolean useForType(JavaType t) {
// ❌ 当前实现:过滤掉所有集合
if (t.isCollectionLikeType()) {
return false;
}
// ✅ 改进方案:保留包含消息的集合的类型信息
if (t.isCollectionLikeType()) {
// 检查集合的元素类型
JavaType contentType = t.getContentType();
if (contentType != null &&
contentType.isTypeOrSubTypeOf(Message.class)) {
return true; // ← 为消息集合保留类型信息
}
return false;
}
return super.useForType(t);
}
方案4:框架侧 - 使用更精确的 TypeReference
反序列化时使用更精确的类型:
// ❌ 当前实现
public Map<String, Object> readData(ObjectInput in) {
String json = Serializer.readUTF(in);
return objectMapper.readValue(json,
new TypeReference<Map<String, Object>>() {});
// ↑ 太宽泛
}
// ✅ 改进方案:根据 JSON 内容动态选择类型
public Map<String, Object> readData(ObjectInput in) {
String json = Serializer.readUTF(in);
// 如果 JSON 包含 messages 字段,使用更精确的类型
JsonNode root = objectMapper.readTree(json);
if (root.has("messages")) {
// 使用支持消息类型的自定义反序列化器
return objectMapper.readValue(json, MESSAGE_AWARE_TYPE_REF);
}
return objectMapper.readValue(json, DEFAULT_TYPE_REF);
}
方案5:框架侧 - 提供配置验证
在构造函数中验证传入的 ObjectMapper:
public SpringAIJacksonStateSerializer(
AgentStateFactory<OverAllState> stateFactory,
ObjectMapper objectMapper) {
super(stateFactory, objectMapper);
// ✅ 验证 ObjectMapper 是否正确配置
if (!isObjectMapperCompatible(objectMapper)) {
throw new IllegalArgumentException(
"The provided ObjectMapper is not compatible with " +
"SpringAIJacksonStateSerializer. Please use the default " +
"constructor or ensure your ObjectMapper has proper type " +
"handling configured.");
}
// ... 继续配置
}
private boolean isObjectMapperCompatible(ObjectMapper mapper) {
// 测试序列化/反序列化一个包含消息的状态
try {
Map<String, Object> testState = Map.of(
"messages", List.of(new UserMessage("test"))
);
String json = mapper.writeValueAsString(testState);
Map<String, Object> result = mapper.readValue(json,
new TypeReference<Map<String, Object>>() {});
Object messages = result.get("messages");
if (messages instanceof List) {
List<?> list = (List<?>) messages;
if (!list.isEmpty() && list.get(0) instanceof Message) {
return true; // ✅ 配置正确
}
}
} catch (Exception e) {
// 序列化失败
}
return false; // ❌ 配置不兼容
}
测试验证
我在测试代码中成功复现了这个问题:
@Test
void testDirectRedisDeserialization_WithCustomObjectMapper() {
System.out.println("演示 LinkedHashMap 问题");
// 创建自定义 ObjectMapper(模拟用户配置)
ObjectMapper customMapper = Jackson2ObjectMapperBuilder.json()
.modules(new JavaTimeModule())
.build();
// 使用自定义 RedissonClient
Config config = new Config();
config.setCodec(new JsonJacksonCodec(customMapper));
RedissonClient customClient = Redisson.create(config);
// 存储消息
List<Object> messages = new ArrayList<>();
Map<String, Object> userMsg = new LinkedHashMap<>();
userMsg.put("content", "User question");
userMsg.put("messageType", "USER");
messages.add(userMsg);
RBucket<Object> bucket = customClient.getBucket("test-messages");
bucket.set(messages);
System.out.println("✅ Stored to Redis");
// 读取消息
Object retrieved = bucket.get();
System.out.println("Retrieved type: " + retrieved.getClass());
if (retrieved instanceof List) {
List<?> list = (List<?>) retrieved;
Object first = list.get(0);
System.out.println("First element type: " + first.getClass().getName());
if (first instanceof LinkedHashMap) {
System.out.println("✅ Successfully reproduced issue #3980!");
System.out.println(" Messages were deserialized as LinkedHashMap");
// 尝试类型转换
try {
AbstractMessage msg = (AbstractMessage) first;
fail("Should have thrown ClassCastException");
} catch (ClassCastException e) {
System.out.println("❌ ClassCastException occurred:");
System.out.println(" " + e.getMessage());
}
}
}
bucket.delete();
}
输出:
演示 LinkedHashMap 问题
✅ Stored to Redis
Retrieved type: class java.util.ArrayList
First element type: java.util.LinkedHashMap
✅ Successfully reproduced issue #3980!
Messages were deserialized as LinkedHashMap
❌ ClassCastException occurred:
class java.util.LinkedHashMap cannot be cast to
class org.springframework.ai.chat.messages.AbstractMessage
六、经验总结
1. 序列化框架的复杂性
这个问题揭示了序列化框架的几个微妙之处:
- 类型擦除:Java 泛型在运行时被擦除,导致类型信息丢失
- 多态序列化:需要显式的类型标记(如
@class) - 配置依赖:序列化和反序列化必须使用兼容的配置
- 默认行为:Jackson 在缺少类型信息时的后备策略
2. 框架设计原则
这个案例也展示了好的框架应该遵循的原则:
✅ 应该做的:
-
明确的 API 契约
- 如果提供了参数,就应该正确处理
- 不应该暗中修改用户的配置
-
防御性编程
- 验证输入参数
- 提供有用的错误信息
- 在问题发生前就捕获
-
完善的文档
- 说明哪些配置是必需的
- 警告潜在的陷阱
- 提供最佳实践示例
-
向后兼容
- 不破坏现有代码
- 提供迁移路径
- 保持 API 稳定
❌ 不应该做的:
-
暗中修改用户配置
- 用户传入的对象被框架修改
- 没有明确说明
-
依赖隐式约定
- 期望用户"知道"正确的配置
- 没有验证机制
-
宽泛的类型定义
TypeReference<Map<String, Object>>- 丢失了重要的类型信息
3. 对开发者的建议
如果你正在使用 Spring AI Alibaba:
-
优先使用默认配置
// ✅ 推荐 new SpringAIJacksonStateSerializer(OverAllState::new); // ⚠️ 谨慎使用 new SpringAIJacksonStateSerializer(OverAllState::new, customMapper); -
需要自定义时,基于默认配置扩展
SpringAIJacksonStateSerializer serializer = new SpringAIJacksonStateSerializer(OverAllState::new); ObjectMapper mapper = serializer.objectMapper(); mapper.registerModule(myCustomModule); -
测试序列化/反序列化
@Test void testSerializationRoundTrip() { Map<String, Object> original = ...; String json = mapper.writeValueAsString(original); Map<String, Object> deserialized = mapper.readValue(json, typeRef); // 验证类型正确 Object messages = deserialized.get("messages"); assertTrue(messages instanceof List); List<?> list = (List<?>) messages; assertTrue(list.get(0) instanceof Message); }
4. 对框架开发者的建议
如果你正在开发类似的框架:
-
API 设计
- 不提供可能出错的选项
- 或者提供但要严格验证
- 考虑使用 Builder 模式限制配置
-
错误处理
- 在问题发生时提供清晰的错误信息
- 说明问题原因和解决方法
- 考虑添加诊断工具
-
文档
- 说明每个参数的含义和约束
- 提供正反示例
- 解释底层机制
-
测试
- 覆盖各种配置组合
- 测试边缘情况
- 包含反例测试(应该失败的情况)
七、结语
本文介绍了一个经典的序列化框架问题,它揭示了:
- 问题本质:
useForType过滤掉集合类型 +TypeReference<Map<String, Object>>过于宽泛 = LinkedHashMap - 责任归属:虽然用户可以避免,但本质上是框架设计问题
- 解决方案:用户侧使用默认配置,框架侧改进 API 设计
希望这篇文章能帮助你理解这个问题的来龙去脉。如果你在使用 Spring AI Alibaba 时遇到类似问题,现在你知道该怎么办了!
附录:关键代码位置索引
| 组件 | 文件 | 行号 | 说明 |
|---|---|---|---|
| 序列化 | JacksonStateSerializer.java |
95-101 | writeData() |
| 反序列化 | JacksonStateSerializer.java |
104-108 | readData() |
| 类型过滤 | SpringAIJacksonStateSerializer.java |
72-75 | useForType() |
| Redis 存储 | RedisSaver.java |
289 | bucket.set() |
| Redis 读取 | RedisSaver.java |
200-201 | bucket.get() |
| 类型转换 | AgentLlmNode.java |
149 | 强制转换位置 |
| 测试验证 | RedisSaverIssue3980Test.java |
264-360 | 问题复现 |
更多推荐



所有评论(0)