2012-02-02 22 views
9

在我們的數據結構(使用默認機制(無自定義writeObject/readObject))的反序列化期間,出現ImmutableMap $ SerializedForm(來自谷歌的Guava庫)的一個實例。readResolve無法正常工作? :一個Guava的SerializedForm實例出現

這樣的實例不應該從番石榴的客戶端可見,因爲使用readResolve替換了SerializedForm的實例(請參閱com.google.common.collect.ImmutableMap類中的「writeReplace」)。

因此反序列化失敗,出現以下消息:

java.lang.ClassCastException: cannot assign instance of com.google.common.collect.ImmutableMap$SerializedForm 
to field .. of type java.util.Map in instance of com.blah.C 

這是正確的,因爲ImmutableMap $ SerializedForm不是java.util.Map的一個亞型,但 它應該被取代。出了什麼問題?

我們在類com.blah.C中沒有自定義writeObject/readObject。我們在父對象(包含com.blah.C)中有自定義的序列化代碼。

更新,這裏的堆棧跟蹤的頂部:

java.lang.ClassCastException: cannot assign instance of com.google.common.collect.ImmutableSet$SerializedForm to field com.blah.ast.Automaton.bodyNodes of type java.util.Set in instance of com.blah.ast.Automaton 
at java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2039) 
at java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.java:1212) 
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1952) 
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1870) 
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752) 
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328) 
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350) 
at java.util.ArrayList.readObject(ArrayList.java:593) 
at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:597) 
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974) 
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1848) 
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752) 
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328) 
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1946) 
at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:479) 
at com.blah.ast.AstNode.readObject(AstNode.java:189) 
at sun.reflect.GeneratedMethodAccessor10.invoke(Unknown Source) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:597) 
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974) 
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1848) 
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752) 
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328) 
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350) 
at java.util.ArrayList.readObject(ArrayList.java:593) 
at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:597) 
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974) 
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1848) 
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1752) 
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328) 
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1946) 
at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:479) 
at com.blah.ast.AstNode.readObject(AstNode.java:189) 
+0

你能顯示完整的堆棧跟蹤? – axtavt 2012-02-02 11:04:07

回答

5

本週,我們再次面對這個bug;但我找到了根源。 ObjectInputStream使用的類加載器是高度依賴的(有些人會說不確定性)。下面是Sun的文檔的相關部分(它是從ObjectInputStream的#resolveClass(ObjectStreamClass的)的摘錄):

[類加載]被確定爲如下:如果在當前線程的堆棧,其聲明類上的方法是由用戶定義的類加載器定義的(並且不是爲了實現反射調用而生成的),那麼它是與當前正在執行的幀最接近的這種方法對應的類加載器;否則,它是空的。如果此調用導致ClassNotFoundException,並且傳遞的ObjectStreamClass實例的名稱是基本類型或void的Java語言關鍵字,則將返回表示該基元類型或void的Class對象(例如,名爲「int」的ObjectStreamClass 「將解析爲Integer.TYPE)。否則,ClassNotFoundException將拋出給此方法的調用者。

在我們的應用中,我們必須依賴於一個只有公用事業插件A.我們反序列化對象,它們的類B中一個Eclipse插件B,但反序列化是一個開始(創建ObjectInputStream的存在),和這是問題所在。很少(即根據文檔說的取決於調用堆棧)反序列化選擇了錯誤的類加載器(無法加載B類的加載器)。爲了解決這個問題,我們通過從頂級反序列化調用者(在B)適當的裝載機在A.這種方法現在使用自定義的ObjectInputStream如下實用方法(注意是自由變量「裝載機」):

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)) { 
       @SuppressWarnings("rawtypes") 
       @Override 
       protected Class resolveClass(ObjectStreamClass objectStreamClass) 
         throws IOException, ClassNotFoundException { 
        return Class.forName(objectStreamClass.getName(), true, loader); 
       } 
      }; 
+0

我也在OSGi環境中看到過這個。我有一個bundle負責數據庫層的[de]序列化,我無法控制如何使用類加載器。因此,我不得不設置DynamicImport-Package:*以確保可以看到所有類。不幸的是,我忘記了從包含需要反序列化的類的包中導出包,並忘記了在此類中實現序列化代理。我運行了一個調試器,我也看到了發生了ClassNotFoundException。恥辱這不報! – 2013-05-03 18:14:18

3

我們發現瞭如何避免該錯誤,但沒有找到造成它的原因。

當我們反序列化ArrayListMultiMap的一個實例時,類加載器找不到我們的類(com.blah ....),因爲使用Guava的類加載器(在代碼中調用ObjectInputStream#resolveClass)而不是默認值類加載器。然後,ObjectInputStream通過使用ClassCastExceptions填充HandleList#條目的實例來傳播失敗。這種異常最終導致readResolve被跳過,這就解釋了爲什麼ImmutableMap $ SerializedForm出現。

奇怪的是,我們序列化和反序列化了很多其他數據結構(包括我們自己的和番石榴的)。序列化guava的ArrayListMultimap自己(使用自定義的writeObject)可以避免該錯誤(即使我們序列化Guava集合的實例(儘管不是Multimaps))。

我們不明白爲什麼類加載器突然變得錯誤,但是一個bug必須潛伏在某個地方。我相信我們收到了ClassCastException而不是ClassNotFoundException,因爲ObjectInputStream中的錯誤處理是錯誤的(即使某些類丟失,readResolve也不應該被跳過)。

2

的問題是,writeReplace()/的readResolve()沒能很好的與您的對象圖循環引用玩。 writeReplace()和readResolve()是不對稱的。在序列化期間,Java將替換所有引用,包括循環引用。但在反序列化過程中,Java不會解析循環引用。這是不幸的設計。從the serialization spec

注意 - 的readResolve方法不是在對象上調用,直到 對象被完全構造,所以在其 對象圖此對象的任何引用將不會被更新爲 提名的新對象的readResolve。然而,隨着 writeReplace方法的對象的序列化過程中,要在 置換對象的對象圖中的原始對象的所有引用將被替換到 置換對象的引用。因此,在其中對象是 序列提名的替換對象,其對象圖具有 參考原始對象的情況下,反序列化將導致對象的不正確 圖表。此外,如果正被讀取的 對象的引用類型(由writeReplace提名)和原始對象 是不兼容的,對象圖的結構將提高一個 ClassCastException異常。

Guava開發人員可以通過使ImmutableMap $ SerializedForm擴展ImmutableMap和委託給適當的ImmutableMap實例來解決此問題。當發生循環引用時,調用者將獲得SerializedForm而不是直接引用ImmutableMap,但它比ClassCastException更好。

0

有同樣的問題。原來,不可變列表的成員對象的類不在反序列化方的類路徑中。 但是這個事實隱藏在ClassCastException的後面。

我現在做更好的錯誤檢測與這個結構:

final ImmutableSet.Builder<Object> notFoundClasses = ImmutableSet.builder(); 
    try { 
     ObjectInputStream objectInputStream = new ObjectInputStream(inputStream) { 
      @Override 
      protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { 
       try { 
        return super.resolveClass(desc); 
       } catch (ClassNotFoundException e) { 
        notFoundClasses.add(desc.getName()); 
        throw e; 
       } 
      } 
     }; 
     return (T) objectInputStream.readObject(); 
    } catch (ClassCastException e) { 
     throw Exceptions.runtime(e, "ClassCastException while de-serializing '%s', classes not found are: %s", objectClass, notFoundClasses.build()); 
    } catch (IOException | ClassNotFoundException e) { 
     throw Exceptions.runtime(e, "Could not de-serialize '%s'", objectClass); 
    }