readResolve funktioniert nicht? : Eine Instanz von Guavas SerializedForm erscheint

9

Während der Deserialisierung einer unserer Datenstrukturen (unter Verwendung des Standardmechanismus (kein benutzerdefiniertes writeObject / readObject)) erscheint eine Instanz von ImmutableMap $ SerializedForm (aus der Guava-Bibliothek von Google).

Eine solche Instanz sollte von Clients von Guava nicht sichtbar sein, da Instanzen von SerializedForm durch readResolve ersetzt werden (siehe zum Beispiel "writeReplace" in der Klasse com.google.common.collect.ImmutableMap).

Daher schlägt die Deserialisierung mit der folgenden Meldung fehl:

%Vor%

Dies ist richtig, da ImmutableMap $ SerializedForm noch kein Subtyp von java.util.Map ist es hätte ersetzt werden sollen. Was läuft falsch?

Wir haben kein benutzerdefiniertes writeObject / readObject in der Klasse com.blah.C. Wir haben einen benutzerdefinierten Serialisierungscode in übergeordneten Objekten (die com.blah.C enthalten).

update, hier ist der Anfang des Stacktrace:

%Vor%     
Clément Hurlin 02.02.2012, 10:25
quelle

5 Antworten

5

Diese Woche haben wir uns wieder mit diesem Fehler konfrontiert; aber ich habe den Grund gefunden. Der Klassenlader, der von ObjectInputStream verwendet wird, ist stark kontextabhängig (manche würden sagen indeterministisch ). Hier ist der relevante Teil der Sun-Dokumentation (es ist ein Auszug aus ObjectInputStream # resolveClass (ObjectStreamClass)):

  

[Der Klassenlader] wird wie folgt bestimmt: Wenn es eine Methode für den Stack des aktuellen Threads gibt, deren deklarierende Klasse von einem benutzerdefinierten Klassenlader definiert wurde (und nicht zur Implementierung von reflektiven Aufrufen generiert wurde), dann ist dies der Fall der Klassenlader, der der nächsten derartigen Methode zu dem gerade ausgeführten Rahmen entspricht; Andernfalls ist es null. Wenn dieser Aufruf zu einer ClassNotFoundException führt und der Name der übergebenen ObjectStreamClass-Instanz das Java-Schlüsselwort für einen primitiven Typ oder void ist, wird das Klassenobjekt zurückgegeben, das diesen primitiven Typ oder void darstellt (z. B. eine ObjectStreamClass mit dem Namen "int "wird in Integer.TYPE aufgelöst. Andernfalls wird die ClassNotFoundException zum Aufrufer dieser Methode ausgelöst.

In unserer Anwendung haben wir ein Eclipse-Plugin B, das von einem reinen Utility-Plugin A abhängt. Wir deserialisierten Objekte, deren Klassen in B liegen, aber die Deserialisierung wurde in A initiiert (dort ein ObjectInputStream erzeugt), und das war das Problem. In seltenen Fällen (d. H. Abhängig vom Aufruf-Stack, wie das Dokument sagt) wählte die Deserialisierung den falschen Klassenlader (einen, der B-Klassen nicht laden konnte). Um dieses Problem zu lösen, haben wir einen geeigneten Loader vom Deserialisierungsaufruf der obersten Ebene (in B) an die Hilfsmethode in A übergeben. Diese Methode verwendet nun einen benutzerdefinierten ObjectInputStream wie folgt (beachten Sie die freie Variable "loader"):

%Vor%     
Clément Hurlin 22.02.2012, 08:16
quelle
3

Bitte reichen Sie einen Fehler ein: Ссылка

Wenn Sie ein eigenständiges Programm anhängen können, das diesen Fehler für Sie auslöst, würde das helfen!

    
Kevin Bourrillion 02.02.2012 15:09
quelle
3

Wir haben herausgefunden, wie wir den Fehler vermeiden können, aber wir haben nicht gefunden, was ihn verursacht hat.

Wenn wir eine Instanz von ArrayListMultiMap deserialisieren, kann der Klassenlader keine Klasse unserer Klasse (com.blah ....) finden, da Guavas Klassenlader (in Code, der von ObjectInputStream # resolveClass aufgerufen wird) anstelle der Standardklasse verwendet wird Lader. Dann verbreitet ObjectInputStream den Fehler, indem er seine Instanz von HandleList # -Einträgen mit ClassCastExceptions füllt. Solche Ausnahmen führen letztendlich dazu, dass readResolve übersprungen wird, was erklärt, warum ein ImmutableMap $ SerializedForm auftaucht.

Was seltsam ist, ist, dass wir viele andere Datenstrukturen (sowohl unsere eigenen als auch unsere Guaven) serialisieren und deserialisieren. Das Serialisieren von Guaavas ArrayListMultimap selbst (mit einem benutzerdefinierten writeObject) vermeidet den Fehler (auch wenn wir Instanzen von Guava-Sammlungen serialisieren (nicht jedoch Multimaps)).

Wir verstehen nicht, warum der Klassenlader plötzlich falsch liegt, aber irgendwo muss ein Fehler lauern. Ich glaube, wir haben ClassCastException statt ClassNotFoundException erhalten, weil die Fehlerbehandlung in ObjectInputStream falsch ist (readResolve sollte nicht übersprungen worden sein, selbst wenn eine Klasse fehlt).

    
Clément Hurlin 03.02.2012 08:31
quelle
2

Das Problem ist, dass writeReplace () / readResolve () nicht gut mit Zirkelverweisen in Ihrem Objektdiagramm funktioniert. writeReplace () und readResolve () sind asymmetrisch. Während der Serialisierung ersetzt Java alle Referenzen, einschließlich Zirkelverweise. Während der Deserialisierung löst Java jedoch keine Zirkelverweise auf. Dies ist leider beabsichtigt. Aus Serialisierungsspezifikationen :

  

Hinweis - Die readResolve-Methode wird erst dann für das Objekt aufgerufen, wenn das   Objekt ist vollständig konstruiert, so dass alle Hinweise auf dieses Objekt in seiner   Das Objektdiagramm wird nicht auf das neue Objekt aktualisiert, das von   LeseResolve. Während der Serialisierung eines Objekts mit dem   writeReplace-Methode, alle Verweise auf das ursprüngliche Objekt in der   Das Objektdiagramm des Ersatzobjekts wird durch Verweise auf das Objektobjekt ersetzt   Ersatzobjekt. Daher in Fällen, in denen ein Objekt ist   serialisiert nominiert ein Ersatzobjekt, dessen Objektgraph a hat   Referenz auf das ursprüngliche Objekt, führt die Deserialisierung zu einem   Falscher Graph von Objekten. Wenn die Referenztypen der   Objekt wird gelesen (nominiert von writeReplace) und das ursprüngliche Objekt   sind nicht kompatibel, die Konstruktion des Objektdiagramms erhöht a   ClassCastException.

Die Guava-Entwickler könnten dieses Problem umgehen, indem sie ImmutableMap SerializedForm ImmutableMap erweitern und an die richtige ImmutableMap-Instanz delegieren. Wenn ein Zirkelverweis auftritt, erhält der Aufrufer das SerializedForm-Objekt anstelle eines direkten Verweises auf die ImmutableMap, aber es ist besser als eine ClassCastException.

    
Bob Lee 06.09.2013 00:17
quelle
0

Hatte das gleiche Problem. Es stellte sich heraus, dass die Klasse der Memberobjekte einer unveränderbaren Liste nicht auf dem Klassenpfad der Deserialisierungsseite lag. Aber diese Tatsache war hinter der ClassCastException verborgen.

Jetzt mache ich bessere Fehlererkennung mit diesem Konstrukt:

%Vor%     
oae 30.03.2016 12:54
quelle