Question

Je dois travailler avec un grand nombre de classes Java compilées qui ne spécifiaient pas explicitement un serialVersionUID. Du fait que leurs compilateurs ont été générés de manière arbitraire par le compilateur, de nombreuses classes devant être sérialisées et désérialisées aboutissent à des exceptions, même si les définitions de classes réelles correspondent. (Ceci est tout comportement attendu, bien sûr.)

Il n’est pas pratique pour moi de revenir en arrière et de corriger tout ce code tiers.

Par conséquent, ma question est la suivante: existe-t-il un moyen de faire en sorte que le runtime Java ignore les différences entre serialVersionUIDs, et ne parvienne pas à se désérialiser lorsqu'il existe de réelles différences de structure?

Était-ce utile?

La solution

Si vous avez accès à la base de code, vous pouvez utiliser la tâche SerialVer pour Ant à insérer et à modifiez le serialVersionUID dans le code source d'une classe sérialisable et résolvez le problème une fois pour toutes.

Si vous ne pouvez pas, ou si ce n'est pas une option (par exemple, si vous avez déjà sérialisé certains objets que vous devez désérialiser), une solution serait d'étendre ObjectInputStream . Augmentez son comportement pour comparer le serialVersionUID du descripteur de flux avec le serialVersionUID de la classe dans la JVM locale que ce descripteur représente et pour utiliser le descripteur de classe local en cas de non concordance. . Ensuite, utilisez simplement cette classe personnalisée pour la désérialisation. Quelque chose comme ça (crédit de ce 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;
    }
}

Autres conseils

Dans quelle mesure est-ce difficile à résoudre? Si vous avez le code source et pouvez le reconstruire, ne pouvez-vous pas exécuter un script sur la base de code entière pour insérer un

private long serialVersionUID = 1L;

partout?

Utilisez CGLIB pour les insérer dans les classes binaires?

Les erreurs de sérialisation au moment de l'exécution vous indiquent explicitement ce que l'ID est censé être. Modifiez simplement vos classes pour les déclarer comme identifiant et tout ira bien. Cela implique que vous apportiez des changements, mais je ne crois pas que cela puisse être évité.

Vous pouvez éventuellement utiliser Aspectj pour "introduire" le champ dans chaque classe sérialisable au fur et à mesure de son chargement. Je voudrais d’abord introduire une interface de marqueur dans chaque classe en utilisant le paquet, puis introduire le champ en utilisant un hachage du fichier de classe pour le 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;
   }
}

Vous devrez ajouter l’ l'agent de tissage au moment du chargement aspectj à la VM afin qu’elle puisse intégrer les conseils à vos classes tierces existantes. C’est drôle, mais une fois que vous avez mis en place Aspectj, c’est remarquable le nombre d'utilisations que vous allez faire.

HTH

ste

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top