2009-11-29 11 views
12

devo lavorare con un gran numero di classi Java compilate che non prevedeva esplicitamente un serialVersionUID. Poiché i loro UID sono stati generati arbitrariamente dal compilatore, molte delle classi che devono essere serializzate e deserializzate finiscono per causare eccezioni, anche se le attuali definizioni di classe coincidono. (Questo è tutto comportamento previsto, naturalmente.)Fare in modo che Java runtime ignori serialVersionUIDs?

Non è pratico per me di tornare indietro e correggere tutto questo codice di terze parti.

Pertanto, la mia domanda è: esiste un modo per rendere Java runtime ignorato le differenze in serialVersionUIDs e non riescono a deserializzare solo quando esistono reali differenze nella struttura?

risposta

27

Se si ha accesso al codice di base, è possibile utilizzare SerialVer task for Ant per inserire e modificare serialVersionUID nel codice sorgente di una classe serializzabile e risolvi il problema una volta per tutte.

Se non è possibile o se questa non è un'opzione (ad esempio se sono già stati serializzati alcuni oggetti che è necessario deserializzare), una soluzione potrebbe essere quella di estendere ObjectInputStream. Aumentare il suo comportamento per confrontare la serialVersionUID del descrittore flusso con la serialVersionUID della classe nella JVM locale che questo descrittore rappresenta e utilizzare il descrittore di classe locale in caso di mancata corrispondenza. Quindi, usa questa classe personalizzata per la deserializzazione. Qualcosa di simile a questo (crediti a 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

Questo codice mi ha salvato giorni! Molte grazie! – peceps

+0

L'ultimo collegamento al messaggio è rotto? (dopo sei anni, non sono enormemente sorpreso, attenzione) –

+0

Mille grazie. Questo frammento ha infatti salvato la giornata anche per me. – nserror

2

Come poco pratico è questo per fissare? Se si dispone la fonte e possibile ricostruire, si può non solo eseguire uno script sul intera codebase di inserire un

private long serialVersionUID = 1L; 

ovunque?

2

Usa CGLIB per inserirli nelle classi binarie?

+0

buona idea, ma ... che valore si inserisce esattamente? È necessario leggere il serialVersionUID della versione serializzata. Questa è la parte difficile. –

+0

Oh, whoops. Sì, avresti bisogno di sapere cosa c'è là fuori. – bmargulies

1

Gli errori di serializzazione in fase di esecuzione ti dicono esplicitamente ciò che l'ID dovrebbe essere. Basta cambiare le tue classi per dichiararle come ID e tutto andrà bene. Ciò non implica che sia possibile apportare modifiche, ma non credo che ciò possa essere evitato

0

Si potrebbe utilizzare AspectJ per 'introdurre' il campo in ogni classe serializzabile come viene caricato. Desidero in primo luogo introdurre un'interfaccia di marcatore in ogni classe utilizzando il pacchetto e poi introdurre il campo utilizzando un hash del file di classe per il serialVersionUID

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; 
    } 
} 

Sarà necessario aggiungere il aspectj load time weaver agent al VM in modo che possa tessere la consigli per le tue classi di terze parti esistenti. È divertente anche se una volta che hai deciso di impostare Aspectj, è notevole il numero di usi che dovrai utilizzare.

HTH

ste

+0

Questa è una buona idea, ma non è abbastanza. Il problema non è quello di aggiungere un serialVersionUID, il problema è aggiungere lo stesso serialVersionUID come nella versione serializzata. –