2009-11-29 9 views
12

Ich muss mit einer großen Anzahl kompilierter Java-Klassen arbeiten, die nicht explizit eine serialVersionUID angeben. Da ihre UIDs willkürlich vom Compiler generiert wurden, verursachen viele der Klassen, die serialisiert und deserialisiert werden müssen, Ausnahmen, obwohl die tatsächlichen Klassendefinitionen übereinstimmen. (Dies ist alles erwartete Verhalten, natürlich.)Lassen Sie die Java-Laufzeitumgebung serialVersionUIDs ignorieren?

Es ist unpraktisch für mich, zurückzugehen und all diesen 3rd-Party-Code zu beheben.

Daher meine Frage ist: Gibt es eine Möglichkeit, die Java-Laufzeitumgebung Unterschiede Unterschiede in serialVersionUIDs zu machen, und nur deserialize fehlschlagen, wenn es tatsächliche Unterschiede in der Struktur gibt?

Antwort

27

Wenn Sie Zugriff auf die Codebasis haben, können Sie die SerialVer task for Ant einfügen und die serialVersionUID im Quellcode einer serialisierbaren Klasse ändern und repariere das Problem ein für allemal.

Wenn dies nicht möglich ist oder wenn dies keine Option ist (z. B. wenn Sie bereits einige Objekte serialisiert haben, die Sie deserialisieren müssen), wäre eine Lösung, ObjectInputStream zu erweitern. Erweitern Sie ihr Verhalten, um den serialVersionUID des Stream-Deskriptors mit dem serialVersionUID der Klasse in der lokalen JVM zu vergleichen, die dieser Deskriptor darstellt, und den lokalen Klassendeskriptor im Falle einer Nichtübereinstimmung zu verwenden. Verwenden Sie dann diese benutzerdefinierte Klasse für die Deserialisierung. So etwas wie diese (Kredite an this message):

import java.io.IOException; 
import java.io.InputStream; 
import java.io.InvalidClassException; 
import java.io.ObjectInputStream; 
import java.io.ObjectStreamClass; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 


public class DecompressibleInputStream extends ObjectInputStream { 

    private static Logger logger = LoggerFactory.getLogger(DecompressibleInputStream.class); 

    public DecompressibleInputStream(InputStream in) throws IOException { 
     super(in); 
    } 

    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { 
     ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor 
     Class localClass; // the class in the local JVM that this descriptor represents. 
     try { 
      localClass = Class.forName(resultClassDescriptor.getName()); 
     } catch (ClassNotFoundException e) { 
      logger.error("No local class for " + resultClassDescriptor.getName(), e); 
      return resultClassDescriptor; 
     } 
     ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass); 
     if (localClassDescriptor != null) { // only if class implements serializable 
      final long localSUID = localClassDescriptor.getSerialVersionUID(); 
      final long streamSUID = resultClassDescriptor.getSerialVersionUID(); 
      if (streamSUID != localSUID) { // check for serialVersionUID mismatch. 
       final StringBuffer s = new StringBuffer("Overriding serialized class version mismatch: "); 
       s.append("local serialVersionUID = ").append(localSUID); 
       s.append(" stream serialVersionUID = ").append(streamSUID); 
       Exception e = new InvalidClassException(s.toString()); 
       logger.error("Potentially Fatal Deserialization Operation.", e); 
       resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization 
      } 
     } 
     return resultClassDescriptor; 
    } 
} 
+1

Dieser Code hat mich Tage gerettet! Danke vielmals! – peceps

+0

Ist die letzte Nachrichtenverbindung unterbrochen? (nach sechs Jahren bin ich nicht sehr überrascht, mind) –

+0

Vielen Dank. Dieser Ausschnitt hat mir auch den Tag gerettet. – nserror

2

Wie unpraktisch ist das zu beheben? Wenn Sie die Quelle haben und wieder aufbauen können, können Sie nicht nur ein Skript über die gesamte Code-Basis betreiben ein

private long serialVersionUID = 1L; 

überall einfügen?

2

Verwenden Sie CGLIB, um sie in die Binärklassen einzufügen?

+0

Gute Idee, aber ... Welchen Wert legen Sie genau? Sie müssen die serialVersionUID der serialisierten Version lesen. Das ist der schwierige Teil. –

+0

Oh, hoppla. Ja, du musst wissen, was da draußen ist. – bmargulies

1

Die Serialisierungsfehler zur Laufzeit sagen Ihnen explizit, was die ID erwartet. Ändern Sie einfach Ihre Klassen, um diese als ID zu deklarieren und alles wird in Ordnung sein. Dies bedeutet, dass Sie Änderungen vornehmen können, aber ich glaube nicht, dass dies vermieden werden kann.

0

Sie könnten möglicherweise aspectj verwenden, um das Feld in jede serializable Klasse ‚einführen‘, wie es geladen wird. Ich würde zuerst eine Markierungsschnittstelle in jeder Klasse einführen das Paket und anschließend das Feld einführen

public aspect SerializationIntroducerAspect { 

    // introduce marker into each class in the org.simple package 
    declare parents: (org.simple.*) implements SerialIdIntroduced; 

    public interface SerialIdIntroduced{} 

    // add the field to each class marked with the interface above. 
    private long SerialIdIntroduced.serialVersionUID = createIdFromHash(); 

    private long SerialIdIntroduced.createIdFromHash() 
    { 
     if(serialVersionUID == 0) 
     { 
      serialVersionUID = getClass().hashCode(); 
     } 
     return serialVersionUID; 
    } 
} 

einen Hash der Klassendatei für die serialVersionUID mit Sie das aspectj load time weaver agent an die VM hinzufügen müssen, damit es die weben Beratung in Ihre bestehenden 3rd-Party-Klassen. Es ist zwar lustig, wenn man Aspectj erst einmal einsetzt, aber es ist bemerkenswert, wie viele Anwendungen man verwenden wird.

HTH

ste

+0

Das ist eine gute Idee, aber das ist nicht genug. Das Problem besteht darin, keine serialVersionUID hinzuzufügen. Das Problem besteht darin, dieselbe serialVersionUID wie in der serialisierten Version hinzuzufügen. –

Verwandte Themen