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     }

 通过断点调试看到: 

dubbo序列化框架_List

 

dubbo序列化框架_List_02


dubbo序列化框架_dubbo序列化框架_03

 说明:上面这个是我们预期获取的类,这个是我修复之后的截图,原先产生问题的时候这个地方出现的是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());