Dubbo RPC开发中的序列化问题:深度解析反序列化导致的HashMap异常
最近在使用rpc的时候发现,解析后获取到的对象里面list成员属性值居然是个hashMap。导致其既不能当成hashMap也不能当成List结构来使用。后来通过调试发现这个问题的根源在于自己编码中深度拷贝的问题,导致反序列化过程中找不到指定对象,导致默认解析成了HashMap。在这篇博客中,我们将深入分析这个问题。
问题产生:
List<JobListRpcRes> 其中JobListRpcRes中包含:
private List<ShowTagRpcRes> showTags
说明:希望处理的响应结果是一个List<ShowTagRpcRes>
,但实际上得到的却是一个包含HashMap的List。
一、排查定位过程
1. Hessian: com.alibaba.com.caucho.hessian.io.SerializerFactory
由于在Dubbo中,序列化和反序列化是通过Hessian库来完成的,就照着这个思路排查到,这个过程是通过SerializerFactory
类的getDeserializer
方法来完成的
获取相应的反序列化器 Deserializer
位置1:
/com/alibaba/com/caucho/hessian/io/SerializerFactory.class:247
1 public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
2 Deserializer deserializer = (Deserializer)_staticDeserializerMap.get(cl);
3
4 ...
5
6 //如果能够获取到反序列化器,那么就将反序列化器加入到缓存的反序列化器map中,然后返回反序列化器
7 if (this._cachedDeserializerMap == null) {
8 this._cachedDeserializerMap = new ConcurrentHashMap(8);
9 }
10 //如果类型无效,或者在无法找到反序列化器的map中,那么就返回null
11 this._cachedDeserializerMap.put(cl, deserializer);
12 return (Deserializer)deserializer;
13 }
14 }
位置2: /com/alibaba/com/caucho/hessian/io/SerializerFactory.class:343
1 public Deserializer getDeserializer(String type) throws HessianProtocolException {
2 if (type != null && !type.equals("") && !this._typeNotFoundDeserializerMap.containsKey(type)) {
3 Deserializer deserializer = (Deserializer)_staticTypeMap.get(type);
4 if (deserializer != null) {
5 return (Deserializer)deserializer;
6 } else {
7 if (type.startsWith("[")) {
8 Deserializer subDeserializer = this.getDeserializer(type.substring(1));
9 if (subDeserializer != null) {
10 deserializer = new ArrayDeserializer(subDeserializer.getType());
11 } else {
12 deserializer = new ArrayDeserializer(Object.class);
13 }
14 } else if (_unrecognizedTypeCache.get(type) == null) {
15 try {
16 //加载该类型的 Class 对象
17 Class cl = this.loadSerializedClass(type);
//通过递归调用,获取相应的反序列化器
18 deserializer = this.getDeserializer(cl);
19 } catch (Exception var4) {
20 log.warning("Hessian/Burlap: '" + type + "' is an unknown class in " + this._loader + ":\n" + var4);
21 this._typeNotFoundDeserializerMap.put(type, PRESENT);
22 log.log(Level.FINER, var4.toString(), var4);
23 _unrecognizedTypeCache.put(type, new AtomicLong(1L));
24 }
25 } else {
26 ((AtomicLong)_unrecognizedTypeCache.get(type)).incrementAndGet();
27 if (((AtomicLong)_unrecognizedTypeCache.get(type)).get() % 2000L == 0L) {
28 ((AtomicLong)_unrecognizedTypeCache.get(type)).getAndSet(1L);
29 }
30 }
31
32 if (deserializer != null) {
33 if (this._cachedTypeDeserializerMap == null) {
34 this._cachedTypeDeserializerMap = new ConcurrentHashMap(8);
35 }
36
37 this._cachedTypeDeserializerMap.put(type, deserializer);
38 }
39
40 return (Deserializer)deserializer;
41 }
42 } else {
43 return null;
44 }
45 }
通过断点调试看到:
说明:上面这个是我们预期获取的类,这个是我修复之后的截图,原先产生问题的时候这个地方出现的是ShowTagRpcResDTO,但是这个在rpc里面根本没有,是在rpc中api-impl实现代码中出现的dto,需要进一步转换为我们想要获取的那个类
二、解决办法
1、 强制转换
通过手动进行类型转换。我们可以先将对象序列化为JSON字符串,然后再将JSON字符串反序列化为我们需要的类型。如下所示:
List<JobListRpcRes> jobListRpcResList = JSON.parseArray(JSON.toJSONString(jobRpcResponseList), JobListRpcRes.class);
这样,就可以得到需要的List<JobListRpcRes>
对象了。
2. 从产生问题的编码层面进行修复
1 //注意ShowTagRespDTO的拷贝问题
2 List<JobListRpcRes> data = seoArticleJobRespList.stream().map(s -> {
3 JobListRpcRes jobListRpcRes = XBeanCopier.copyProperties(s, JobListRpcRes.class);
4 List<ShowTagRpcRes> showTagRpcResList = XBeanCopier.copyPropertiesOfList(s.getShowTags(), ShowTagRpcRes.class);
5 jobListRpcRes.setShowTags(showTagRpcResList);
6 return jobListRpcRes;
7
8 }).collect(Collectors.toList());