标题:Dubbo RPC开发中的序列化问题:深度解析反序列化导致的HashMap异常

发布时间 2023-11-09 21:36:09作者: 欢乐豆123

Dubbo RPC开发中的序列化问题:深度解析反序列化导致的HashMap异常

在使用Dubbo RPC进行开发时,我们可能会遇到一些出乎意料的问题。其中之一就是在进行远程调用时,内部嵌套对象出现与预期不符的HashMap。这个问题的根源在于反序列化过程中找不到对象,导致解析成了HashMap。在这篇博客中,我们将深入分析这个问题,并通过调试序列化和反序列化的代码来理解其原因。

问题描述

在我们的项目中,有一次我们遇到了这样一个问题:在进行Dubbo RPC调用时,我们发现返回的结果中,一些内部嵌套的对象被转换成了HashMap,而不是我们预期的类型。这个问题在我们的单元测试中没有出现,只在实际的RPC调用中发生。我们希望处理的响应结果是一个List<JobListRpcResponse>,但实际上我们得到的却是一个包含HashMap的List。

解决办法

解决这个问题的方法是手动进行类型转换。我们可以先将对象序列化为JSON字符串,然后再将JSON字符串反序列化为我们需要的类型。如下所示:

List<JobListRpcResponse> jobListRpcResponseList = JSON.parseArray(JSON.toJSONString(jobRpcResponseList), JobListRpcResponse.class);

这样,我们就可以得到我们需要的List<JobListRpcResponse>对象了。

问题本质

问题的本质在于反序列化过程中找不到对象,导致解析成了HashMap。在Dubbo中,序列化和反序列化是通过Hessian库来完成的。Hessian在反序列化对象时,如果找不到对象的类型,就会将对象解析为HashMap。

分析SerializerFactory关键源码

为了理解这个问题,我们需要深入理解Hessian库是如何获取对象的反序列化器的。这个过程是通过SerializerFactory类的getDeserializer方法来完成的。

 

 

 4 public Deserializer getDeserializer(String type) throws HessianProtocolException {
 5     if (type != null && !type.equals("") && !this._typeNotFoundDeserializerMap.containsKey(type)) {
 6         if (this._cachedTypeDeserializerMap != null) {
 7             Deserializer deserializer = (Deserializer)this._cachedTypeDeserializerMap.get(type);
 8             if (deserializer != null) {
 9                 return deserializer;
10             }
11         }
12 13 在这段代码中,首先检查传入的类型(`type`)是否为空,是否为空字符串,以及该类型是否已经在无法找到反序列化器的map(`_typeNotFoundDeserializerMap`)中。如果类型是有效的,并且没有在无法找到反序列化器的map中,那么就尝试从缓存的反序列化器map(`_cachedTypeDeserializerMap`)中获取该类型的反序列化器。如果能够从缓存中获取到反序列化器,那么就直接返回。
14 
15 ```java
16         Deserializer deserializer = (Deserializer)_staticTypeMap.get(type);
17         if (deserializer != null) {
18             return (Deserializer)deserializer;
19         } else {
20 ```
21 如果缓存中没有该类型的反序列化器,那么就尝试从静态类型map(`_staticTypeMap`)中获取反序列化器。静态类型map中存储的是一些预定义的类型和对应的反序列化器。如果能够从静态类型map中获取到反序列化器,那么就直接返回。
22 
23 ```java
24             if (type.startsWith("[")) {
25                 Deserializer subDeserializer = this.getDeserializer(type.substring(1));
26                 if (subDeserializer != null) {
27                     deserializer = new ArrayDeserializer(subDeserializer.getType());
28                 } else {
29                     deserializer = new ArrayDeserializer(Object.class);
30                 }
31             } else if (_unrecognizedTypeCache.get(type) == null) {
32 ```
33 如果类型是一个数组类型(以"["开头),那么就尝试获取数组元素类型的反序列化器。如果能够获取到数组元素类型的反序列化器,那么就创建一个新的数组反序列化器。否则,就创建一个Object类型的数组反序列化器。
34 
35 ```java
36                 try {
37                     Class cl = this.loadSerializedClass(type);
38                     deserializer = this.getDeserializer(cl);
39                 } catch (Exception var4) {
40                     log.warning("Hessian/Burlap: '" + type + "' is an unknown class in " + this._loader + ":\n" + var4);
41                     this._typeNotFoundDeserializerMap.put(type, PRESENT);
42                     log.log(Level.FINER, var4.toString(), var4);
43                     _unrecognizedTypeCache.put(type, new AtomicLong(1L));
44                 }
45             } else {
46                 ((AtomicLong)_unrecognizedTypeCache.get(type)).incrementAndGet();
47                 if (((AtomicLong)_unrecognizedTypeCache.get(type)).get() % 2000L == 0L) {
48                     ((AtomicLong)_unrecognizedTypeCache.get(type)).getAndSet(1L);
49                 }
50             }
51 ```
52 如果类型不是数组类型,那么就尝试加载该类型的类,并获取该类的反序列化器。如果加载类或获取反序列化器失败,那么就将该类型加入到无法找到反序列化器的map中,并记录警告日志。同时,将该类型加入到未识别类型缓存(`_unrecognizedTypeCache`)中,并设置计数为1。如果该类型已经在未识别类型缓存中,那么就增加计数。如果计数达到2000,那么就重置计数为1。
53 
54 ```java
55             if (deserializer != null) {
56                 if (this._cachedTypeDeserializerMap == null) {
57                     this._cachedTypeDeserializerMap = new ConcurrentHashMap(8);
58                 }
59 
60                 this._cachedTypeDeserializerMap.put(type, deserializer);
61             }
62 
63             return (Deserializer)deserializer;
64         }
65     } else {
66         return null;
67     }
68 }
69 ```
70 最后,如果能够获取到反序列化器,那么就将反序列化器加入到缓存的反序列化器map中。然后返回反序列化器。如果类型无效,或者在无法找到反序列化器的map中,那么就返回null。
71 
72 这段代码的关键在于,如果无法找到对应类型的反序列化器,就会将类型加入到无法找到反序列化器的map中。这就是我们在Dubbo RPC调用中,如果无法找到对象的类型,就会将对象解析为HashMap的原因。

 

结论

在使用Dubbo RPC进行开发时,我们需要注意序列化和反序列化过程中可能出现的问题。特别是当我们在RPC调用中传递复杂的对象时,我们需要确保我们正确地进行类型转换,以防止类型信息丢失。同时,我们也需要深入理解Hessian库的工作原理,以便在遇到问题时,能够快速地找到问题的根源。