标题:Dubbo RPC开发中的序列化问题: RPC响应结果中出现了非预期的HashMap结构
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());