Closed MuShanYu closed 1 year ago
我的redis配置应该是没有问题的。
看起来是使用 jackson
序列化 hutool 的 Tree
的时候报错了,检查一下 Jackson2JsonRedisSerializer
是否支持序列化 hutool 的 Tree
类型的对象,如果不支持,尝试看看为 jackson 添加用于处理 Tree
类型数据的 jackson 序列化器。
我描述一下我遇到此问题的场景,我需要缓存产品分类,产品分类是一个树型结构,所有我想直接在redis中缓存已经生成好的Tree列表。在我尝试自定义还是报出同样的错误,objectMapper.registerModule(new TreeCustomModule());
我将Tree通过fastJson转为字符串。我觉得这不是Jackson2JsonRedisSerializer的问题,它是spirng已经提供的序列化方式实现。可能也许会有和我遇到同样场景的人,希望大佬们能优化一下这个。目前我决定缓存对应的实体列表,最后转为Tree返回。
看起来是使用
jackson
序列化 hutool 的Tree
的时候报错了,检查一下Jackson2JsonRedisSerializer
是否支持序列化 hutool 的Tree
类型的对象,如果不支持,尝试看看为 jackson 添加用于处理Tree
类型数据的 jackson 序列化器。
作者大佬能调试一下这个问题吗,为了使用这个还要对这个类进行特殊处理。
具体的可能还要我试验一下,不过看你的解决办法问题应该还是容器中的 ObjectMapper
不支持序列化 Tree
,能把 TreeCustomModule
具体的内容发出来看一下吗?
如果确实是 Spring 对 ObjectMapper
配置的问题,那么我们可能也没有很好的解决方案。因为 hutool
本身只是一个工具类库,我们并不能确认用户是否有在 spring 应用中需要将 Tree
序列化为 json 的需求,因此站在这个角度,我们并不适合去提供一个 starter
或者基于 spring 的特定序列化器好让容器中的 ObjectMapper
支持序列化 Tree
。
不过,在后续,我们会在文档中强调在这种特殊场景,并且按照你的 issue 给出解决方案,如果用户有这方面的需要,可以直接按照文档解决这个问题。
2023-08-11 补充:
我试了一下,用默认的配置启动(spring-boot-web-starter),注入的 ObjectMapper
是可以序列化 Tree
的:
@Component
public static class Bean {
@Autowired
private ObjectMapper objectMapper;
@SneakyThrows
@PostConstruct
public void doSomething() {
List<TreeNode<Integer>> nodeList = CollUtil.newArrayList();
nodeList.add(new TreeNode<>(1, 1, "杭州市", 0));
nodeList.add(new TreeNode<>(111, 11, "余杭区", 0));
nodeList.add(new TreeNode<>(112, 11, "西湖区", 0));
nodeList.add(new TreeNode<>(113, 11, "拱墅区", 0));
nodeList.add(new TreeNode<>(2, 0, "山东省", 0));
nodeList.add(new TreeNode<>(22, 2, "泰安市", 0));
nodeList.add(new TreeNode<>(222, 22, "泰山区", 0));
Tree<Integer> tree = TreeUtil.buildSingle(nodeList);
System.out.println("Tree: " + objectMapper.writeValueAsString(tree));
// = {"id":0,"children":[{"id":2,"parentId":0,"weight":0,"name":"山东省","children":[{"id":22,"parentId":2,"weight":0,"name":"泰安市","children":[{"id":222,"parentId":22,"weight":0,"name":"泰山区"}]}]}]}
}
}
这应该是自定义序列化问题。
考虑调用Tree转为JSON然后序列化……
具体的可能还要我试验一下,不过看你的解决办法问题应该还是容器中的
ObjectMapper
不支持序列化Tree
,能把TreeCustomModule
具体的内容发出来看一下吗?如果确实是 Spring 对
ObjectMapper
配置的问题,那么我们可能也没有很好的解决方案。因为hutool
本身只是一个工具类库,我们并不能确认用户是否有在 spring 应用中需要将Tree
序列化为 json 的需求,因此站在这个角度,我们并不适合去提供一个starter
或者基于 spring 的特定序列化器好让容器中的ObjectMapper
支持序列化Tree
。不过,在后续,我们会在文档中强调在这种特殊场景,并且按照你的 issue 给出解决方案,如果用户有这方面的需要,可以直接按照文档解决这个问题。
自定义的serializer
public class TreeDataSerializer extends JsonSerializer<Tree> {
@Override
public void serialize(Tree value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(JSON.toJSONString(value));
}
}
自定义的module
@Component
public class TreeCustomModule extends SimpleModule {
public TreeCustomModule() {
addSerializer(Tree.class, new TreeDataSerializer());
}
}
在objectMapper中进行注册
objectMapper.registerModule(new TreeCustomModule());
问题出现在@Autowired注解上面:
@Autowired
public RedisTemplate<String, T> redisTemplate;
我在debug时进行跟踪发现最终使用的是stringRedisTemplate,而非已经进行配置的redisTemplate,导致该问题的是spring的依赖注入和泛型擦除问题。 Autowired默认是byType进行注入,java在运行时会进行泛型擦除,在RedisAutoConfiguration中有StringTemplate和RedisTemplate<Object, Object>两个类,在编译后 T 的具体类型信息在运行时会被擦除,所以 Spring 在处理这个字段时只能看到 RedisTemplate<String, ?> 这种类型。spring再对这两个类的选择上会根据具体的类型和泛型擦除后的信息选择一个具体的实现,由于T类型是未知的,所有spring最终会选择更具体的StringTemplate,导致最后注入的是StringTemplate。 StringRedisTemplate 被spring创建时,它的构造方法初始化了所有的Serializer,最终所使用的序列化均为StringRedisSerializer。 最终才发生了xxx类 cast string的异常。
public StringRedisTemplate() {
setKeySerializer(RedisSerializer.string());
setValueSerializer(RedisSerializer.string());
setHashKeySerializer(RedisSerializer.string());
setHashValueSerializer(RedisSerializer.string());
}
redis的自动配置:
@AutoConfiguration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
ConditionalOnMissingBean是指容器中没有redisTemplate名称的bean是将该类进行创建。 正确的注入方式: 1.去除泛型,spring在根据byType注入时,不会因为泛型擦除问题进行选择,会注入RedisTemplate。 2.不使用模糊泛型,指定具体泛型类型RedisTemplate<Object, Object>,进行正确的类型注入。
这不是Hutool框架的问题,给各位开发者带来了不必要的麻烦,非常抱歉!
这应该是自定义序列化问题。
考虑调用Tree转为JSON然后序列化……
直接存string是没问题的,但是我已经配置了Jackson,没有必要在进行重复的json序列化了。
@MuShanYu 感谢详细的答疑解惑~~
不过我们讨论问题的核心不在于自定义序列化问题,而在于Tree是否有必要自行实现序列化,而不用用户写那么一大堆~~
具体的可能还要我试验一下,不过看你的解决办法问题应该还是容器中的
ObjectMapper
不支持序列化Tree
,能把TreeCustomModule
具体的内容发出来看一下吗?如果确实是 Spring 对
ObjectMapper
配置的问题,那么我们可能也没有很好的解决方案。因为hutool
本身只是一个工具类库,我们并不能确认用户是否有在 spring 应用中需要将Tree
序列化为 json 的需求,因此站在这个角度,我们并不适合去提供一个starter
或者基于 spring 的特定序列化器好让容器中的ObjectMapper
支持序列化Tree
。不过,在后续,我们会在文档中强调在这种特殊场景,并且按照你的 issue 给出解决方案,如果用户有这方面的需要,可以直接按照文档解决这个问题。
2023-08-11 补充: 我试了一下,用默认的配置启动(spring-boot-web-starter),注入的
ObjectMapper
是可以序列化Tree
的:@Component public static class Bean { @Autowired private ObjectMapper objectMapper; @SneakyThrows @PostConstruct public void doSomething() { List<TreeNode<Integer>> nodeList = CollUtil.newArrayList(); nodeList.add(new TreeNode<>(1, 1, "杭州市", 0)); nodeList.add(new TreeNode<>(111, 11, "余杭区", 0)); nodeList.add(new TreeNode<>(112, 11, "西湖区", 0)); nodeList.add(new TreeNode<>(113, 11, "拱墅区", 0)); nodeList.add(new TreeNode<>(2, 0, "山东省", 0)); nodeList.add(new TreeNode<>(22, 2, "泰安市", 0)); nodeList.add(new TreeNode<>(222, 22, "泰山区", 0)); Tree<Integer> tree = TreeUtil.buildSingle(nodeList); System.out.println("Tree: " + objectMapper.writeValueAsString(tree)); // = {"id":0,"children":[{"id":2,"parentId":0,"weight":0,"name":"山东省","children":[{"id":22,"parentId":2,"weight":0,"name":"泰安市","children":[{"id":222,"parentId":22,"weight":0,"name":"泰山区"}]}]}]} } }
非常感谢大佬的调试。应该是我使用的问题。非常感谢回复与帮忙。
@MuShanYu 感谢详细的答疑解惑~~
不过我们讨论问题的核心不在于自定义序列化问题,而在于Tree是否有必要自行实现序列化,而不用用户写那么一大堆~~
了解了,非常感谢各位的回复与解答。
版本情况
JDK版本: Java 11.0.18 hutool版本: 5.X.X(请确保最新尝试是否还有问题)
问题描述(包括截图)
Redis配置Jackson2JsonRedisSerializer序列化器,往redis的list中存入Tree列表,报错class cn.hutool.core.lang.tree.Tree cannot be cast to class java.lang.String
树结构构建成功,但是序列化出问题。
复现代码 RedisConfig配置:
RedisUtil放入list的方法:
调用: