9
votes

Java Serialization - Traitement automatique des champs changés?

Mise à jour du 20 mai: J'aurais d'avoir mentionné que les objets en question ont une définition "SerialVersionSionUID" (la même valeur chez OLLD & NOUVEAU), mais la sérialisation échoue avant que ReadObject () soit appelée à l'exception suivante:

exception dans le fil "MAIN" Java.IO.InvalidClsSException: TestData; Types incompatibles pour le numéro de champ code> p>

Je comprends également un exemple en bas. P>

Je travaille avec une application importante qui envoie des objets sérialisés (implémentations sérialisables, pas exernalisable) du client au serveur. Malheureusement, nous avons maintenant une situation dans laquelle un champ existant a changé de type complètement, ce qui brise la sérialisation. P>

Le plan consiste à mettre à niveau le côté du serveur en premier. À partir de là, j'ai le contrôle des nouvelles versions des objets sérialisés, ainsi que l'ObjectInputStream pour lire les objets (au premier ancien) provenant des clients. P>

J'avais au début de la mise en œuvre de ReadObject ( ) dans la nouvelle version; Cependant, après l'avoir essayé, j'ai découvert que la validation échoue (en raison de types incompatibles) avant la méthode jamais appelée. p>

Si je sous-classe ObjecteInpuTstream, puis-je accomplir ce que je veux? P> Encore mieux, existe-t-il des bibliothèques tierces qui font une sorte de sérialisation 'Magic'? Il serait vraiment intéressant de convertir des outils / bibliothèques pouvant convertir un flux d'objets sérialisés en quelque chose comme un éventail de hashmaps ... sans avoir besoin de charger les objets eux-mêmes. Je ne sais pas si il est possible de le faire (convertir un objet sérialisé sur un hashmap sans charger la définition de l'objet elle-même), mais si cela est possible, je pouvais imaginer un outil pouvant convertir un objet sérialisé (ou flux d'objets ) À une nouvelle version, en utilisant, par exemple, un ensemble de propriétés pour les astuces de conversion / règles, etc. p>

Merci pour toute suggestion. P>

Mise à jour du 20 mai - Exemple de source ci-dessous - le champ "numéro" dans TestData passe à partir d'un "INT" dans l'ancienne version à une "longue" dans la nouvelle version. Remarque ReadObject () Dans la nouvelle version de TestData n'est pas appelée, car une exception est levée avant de ne jamais y arriver: p>

Exception dans le fil "Main" Java.IO.InvalidClsSException: TestDATA; Types incompatibles pour le numéro de champ p>

ci-dessous est la source. Enregistrez-le dans un dossier, puis créez les sous-dossiers 'Old »et« Nouveau ». Mettez la version «ancienne» de la classe TestData dans le dossier «Old» et la nouvelle version dans «Nouveau». Placez les classes «ERITITE» et «Readit» dans le dossier principal (parent de l'ancien / nouveau). 'CD' dans le "vieux" dossier et compiler avec: javac -g -classpath. TestData.java code> ... puis faites la même chose dans le «nouveau» dossier. "CD" retour au dossier parent et compiler Evident / Readit: p> xxx pré>

puis exécuté avec: p> xxx pré>

[Version ancienne] Testdata.java B> P>

import java.io.*;

public class ReadIt
{
    public static void main(String args[])
        throws Exception
    {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("TestData_old.ser"));

        TestData d = (TestData)ois.readObject();

        ois.close();

        System.out.println("Number = " + d.number);
    }
}


4 commentaires

Le jeu de hacks standard de facto ici est de définir explicitement un SerialVersionSionUID pour toute classe mise en œuvre sérialisable, puis remplace la méthode ReadObject. Lorsque des changements se produisent, le code plus récent peut mettre la logique en place pour traiter avec l'ancienne forme


Lisez dans l'ancien objet en utilisant l'ancienne méthode, extrayez-la des données dans le nouvel objet et enregistrez-la.


J'ai oublié de mentionner que j'ai un ensemble "SerialversionSionUid", mais une exception se produit avant que ReadObject () soit appelée dans la nouvelle version. Merci.


Notez également que les données «réelles» en question sont fondamentalement un vecteur d'objets de données plats, où les champs sont des types primitifs ou intégrés (entier, chaîne, etc.) Ce que j'aimerais vraiment avoir, c'est essentiellement la capacité Pour effectuer un traitement de type réfléchissant sur un flux sérialisé, sans avoir à charger une référence (ancienne ou nouvelle) à l'objet en question. Mais peut-être que le format sérialisé ne fournit pas suffisamment d'informations à ce sujet pour faire ce genre de chose.


4 Réponses :


7
votes

Votre problème immédiat semble être que vous n'avez pas défini modifié les chaînes SerialVersionUID pour vos cours. Si vous ne faites pas cela, le code de sérialisation et de désérialisation de l'objet génère les UID basés sur la structure de représentation des types envoyés et reçus. Si vous modifiez le type à une extrémité et non l'autre, les UID ne correspondent pas. Si vous corrigez les UIDS, le code de lecteur aura dépassé la vérification UID et que votre méthode lisaBject aura une chance de "contourner" les différences de données sérialisées.

En dehors de cela, ma suggestion serait:

  • Essayez de changer tous les clients et serveurs en même temps de sorte que vous n'avez pas besoin d'utiliser ReadObject / WriteObject Hacks

  • Si ce n'est pas pratique, essayez de Euffer de l'utilisation d'objets sérialisés Java dans n'importe quoi où il existe une possibilité de correspondances de version lorsque votre système évolue. Utilisez XML, JSON ou quelque chose d'autre qui est moins sensible au "protocole" ou "schéma" changements.

  • Si ce n'est pas pratique, version de votre protocole client / serveur.

    Je doute que vous obtiendrez une traction en sous-classant les classes Objectstream. (Jetez un coup d'œil au code ... dans le code OpenJDK.)


1 commentaires

Notez que j'ai négligé de mentionner que je dispose de «SerialVersionUid» défini (à la même valeur) dans la version ancienne et nouvelle de la classe. J'ai inclus un exemple de code maintenant. Merci pour les commentaires. Je vais voir s'il y a un moyen d'accomplir le premier (Mettre à jour le client / serveur en même temps) - le problème concerne les opérations de 24 heures, etc.



1
votes

Après l'avoir essayé, j'ai découvert que la validation échoue (en raison de types incompatibles) avant la méthode jamais appelée

C'est parce que vous n'avez pas défini un SerialVersionSionUID . C'est facile à réparer. Il suffit d'exécuter l'outil Serialver sur la version ancienne des classes, puis collez la sortie dans le nouveau code source. Vous May puis être capable d'écrire votre méthode lisechatObject () de manière compatible.

En outre, vous pouvez regarder dans l'écriture readresolve () et writerePlace () méthodes ou définissant SERIALPERSISTENTFields de manière compatible, si c'est possible. Voir le spécification de sérialisation de l'objet.

Voir aussi les précieuses suggestions de @Stephenc.

Postez votre modification Je vous suggère de modifier le nom de la variable avec son type. Cela compte comme une suppression plus une insertion, qui est compatible avec la sérialisation et travaillerez aussi longtemps que vous ne voulez pas que l'ancien numéro INT soit lu dans le nouveau nombre long (pourquoi longtemps au lieu de long?). Si vous avez besoin de cela, je vous suggère de laisser un numéro INT et d'ajouter un nouveau champ long / long avec un nom différent et de modifier ReadObject () pour définir le nouveau champ sur 'Number' s'il n'est pas déjà défini par DEFAULTREADOBJECT () .


1 commentaires

Notez que j'ai négligé de mentionner que je dispose de «SerialVersionUid» défini (à la même valeur) dans la version ancienne et nouvelle de la classe. J'ai inclus un exemple de code maintenant. Merci pour les commentaires.



0
votes

Si vous modifiez le type de champ, vous devez modifier son nom. Sinon, vous obtiendrez l'erreur que vous voyez. Si vous souhaitez définir le nouveau nom de champ de l'ancienne Données (ou le calculez d'une autre manière pendant l'objet Lire), vous pouvez faire quelque chose comme ceci:

public class TestData
    implements java.io.Serializable
{
    private static final long serialVersionUID = 2L;

    public Long numberL; //Changed from "int number;"


    private void readObject(final ObjectInputStream ois)
        throws IOException, ClassNotFoundException
    {
        System.out.println("[TestData NEW] readObject() called...");
        ois.defaultReadObject();
        if (numberL == null) {
            // deal with old structure
            GetField fields = ois.readFields();
            numberL = fields.get("number", 0L); // autoboxes old value
        }
    }
}


0 commentaires

3
votes

Vous pouvez continuer à utiliser la sérialisation même lorsque des modifications des classes rendront les anciennes données sérialisées incompatibles avec la nouvelle classe, si vous implémentez externalisable et écrivez un champ supplémentaire indiquant la version avant de rédiger les données de la classe. Cela permet à Readexternal de gérer les anciennes versions de la manière dont vous spécifiez. Je ne connais aucun moyen de le faire automatiquement, mais utiliser cette méthode manuelle peut fonctionner pour vous.

Voici les classes modifiées qui compileront et fonctionnent sans jeter une exception. P>

Old / TestData. java p> xxx pré>

nouveau / testdata.java p> xxx pré>

Ces classes peuvent être exécutées avec les affirmations suivantes p> xxx pré>

changer une classe à mettre en œuvre exernalisable au lieu de sérialisable entraînera l'exception suivante. P>

Exception in thread "main" java.io.InvalidClassException: TestData; Serializable incompatible with Externalizable
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:634)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at ReadIt.main(ReadIt.java:10)


0 commentaires