Dubbo RPC開發中的序列化問題:深度解析反序列化導致的HashMap異常
在使用Dubbo RPC進行開發時,我們可能會遇到一些出乎意料的問題。其中之一就是在進行遠程調用時,內部嵌套對象出現與預期不符的HashMap。這個問題的根源在於反序列化過程中找不到對象,導致解析成了HashMap。在這篇博客中,我們將深入分析這個問題,並通過調試序列化和反序列化的代碼來理解其原因。
問題描述
在我們的項目中,有一次我們遇到了這樣一個問題:在進行Dubbo RPC調用時,我們發現返回的結果中,一些內部嵌套的對象被轉換成了HashMap,而不是我們預期的類型。這個問題在我們的單元測試中沒有出現,只在實際的RPC調用中發生。我們希望處理的響應結果是一個List<JobListRpcResponse>
,但實際上我們得到的卻是一個包含HashMap的List。
解決辦法
解決這個問題的方法是手動進行類型轉換。我們可以先將對象序列化爲JSON字符串,然後再將JSON字符串反序列化爲我們需要的類型。如下所示:
這樣,我們就可以得到我們需要的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庫的工作原理,以便在遇到問題時,能夠快速地找到問題的根源。