Как десериализовать объект сохранился в БД сейчас, когда объект имеет другой serialVersionUID - PullRequest
12 голосов
/ 28 апреля 2009

У моего клиента есть база данных оракула, и объект был сохранен как поле большого двоичного объекта через objOutStream.writeObject, теперь объект имеет другое значение serialVersionUID (даже если у объекта нет изменений, возможно, другая версия jvm) и когда они попытаться десериализовать исключение:

java.io.InvalidClassException: CommissionResult; local class incompatible: 
 stream classdesc serialVersionUID = 8452040881660460728, 
 local class serialVersionUID = -5239021592691549158

Они не присваивали фиксированное значение для serialVersionUID с самого начала, поэтому теперь, когда что-то изменилось, выдается исключение. Теперь они не хотят терять какие-либо данные, для этого, я думаю, лучше всего прочитать объекты, десериализовать их и снова сохранить их с помощью XMLEncoder, чтобы избежать будущих ошибок, таких как текущая ошибка «несовместимость классов».

По-видимому, для этого объекта сохраняются 2 различных значения для serialVersionUID, поэтому я хочу прочитать данные, попробуйте с одним значением, а в случае сбоя попробуйте другое значение. Для этого я попытался изменить serialVersionUID класса, использующего API ASM . Я смог изменить значение, но проблема в том, как активировать изменение в классе, чтобы при десериализации objInpStr.readObject() взять мою модифицированную версию класса с моим конкретным serializedVersionUID. Я создал тестовый класс для имитации реальной среды, я беру объект (который имеет в качестве свойства объект с другой проблемой serialVersionUID), имя объекта - Reservation, свойство - CommissionResult

public class Reservation implements java.io.Serializable {


    private CommissionResult commissionResult = null;

}


public class CommissionResult implements java.io.Serializable{



}


import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.SerialVersionUIDAdder;

public class SerialVersionUIDRedefiner extends ClassLoader {


    public void workWithFiles() {
        try {
            Reservation res = new Reservation();
            FileOutputStream f = new FileOutputStream("/home/xabstract/tempo/res.ser");
        ObjectOutputStream out = new ObjectOutputStream(f);

            out.writeObject(res);

            out.flush();
            out.close();

            ClassWriter cw = new ClassWriter(0); 
             ClassVisitor sv = new SerialVersionUIDAdder(cw); //assigns a real serialVersionUID 
             ClassVisitor ca = new MyOwnClassAdapter(sv); //asigns my specific serialVerionUID value
             ClassReader cr=new  ClassReader("Reservation"); 
              cr.accept(ca, 0); 

             SerialVersionUIDRedefiner   loader= new SerialVersionUIDRedefiner(); 
             byte[] code = cw.toByteArray();
             Class exampleClass =        loader.defineClass("Reservation", code, 0, code.length); //at this point the class Reservation has an especific serialVersionUID value that I put with MyOwnClassAdapter

             loader.resolveClass(exampleClass);
             loader.loadClass("Reservation");
             DeserializerThread dt=new DeserializerThread();
             dt.setContextClassLoader(loader);
             dt.run();
    } catch (Exception e) {
            e.printStackTrace();
    }}



import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class DeserializerThread extends Thread {

    public void run() {
        try {
            FileInputStream f2;

            f2 = new FileInputStream("/home/xabstract/tempo/res.ser");

             ObjectInputStream in = new ObjectInputStream(f2);


            Reservation c1 = (Reservation)in.readObject();



            System.out.println(c1);

        } catch (Exception e) {

            e.printStackTrace();
        }
        stop();
    }
}

MyOwnClassAdapter Relevant code:



public void visitEnd() {
        // asign SVUID and add it to the class

            try {

                cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
                        "serialVersionUID",
                        "J",
                        null,
                        new Long(-11001));//computeSVUID()));
            } catch (Throwable e) {
                e.printStackTrace();
                throw new RuntimeException("Error while computing SVUID for x"
                        , e);
            }


        super.visitEnd();
    }

Проверка должна завершиться неудачей с java.io.InvalidClassException «несовместимым локальным классом» потому что я изменил serialVersionUID после сохранения файла и использовал новый для чтения де файл, но это не дает сбоя, так что это означает, что ObjectInputStream.readObject не используя мою модифицированную версию класса Reservation.

Есть идеи? Заранее спасибо.

!!!!!!!!!!!!! UPDATE:

Хорошо, можно переопределить resultClassDescriptor, чтобы переопределить поток serialVersionUID, но, как я уже сказал, до того, как он там появился, происходит нечто странное две версии класса сохраняются, объекты с serialVersionUID = -5239021592691549158L и другие со значением 8452040881660460728L это последнее значение генерируется, если я не указываю значение для локального класса.

-Если я не указываю значение для serialVersionUID, тогда используется значение по умолчанию (8452040881660460728L), но невозможно десертифицировать объекты, которые имеет другое значение, выдается сообщение о том, что свойство другого типа.

-Если я укажу значение -5239021592691549158L, классы сохранятся с этим значением успешно десериализованы, но не другие, такая же ошибка типов.

это трассировка ошибки:

Потенциально фатальная операция десериализации. java.io.InvalidClassException: Переопределение несоответствия версии сериализованного класса: local serialVersionUID = -5239021592691549158 stream serialVersionUID = 8452040881660460728 java.lang.ClassCastException: невозможно назначить экземпляр java.util.HashMap для поля com.posadas.ic.rules.common.commisionRules.CommissionResult.statusCode типа java.lang.String в экземпляре com.posadas.ic.rules. common.commisionRules.CommissionResult

Когда эта ошибка была выдана, класс имел значение -5239021592691549158, если изменение значение до 8452040881660460728 класса успешно десериализовано, так что же происходит? почему эта ошибка пытается привести к неправильному классу?

Спасибо

Ответы [ 6 ]

17 голосов
/ 28 апреля 2009

Хорхе Я нашел одно решение на http://forums.sun.com/thread.jspa?threadID=518416, которое работает.

Создайте ниже класс в вашем проекте. Когда бы вы ни создавали объект ObjectInputStream, используйте вместо него DecompressibleInputStream, и он десериализует старый объект с новым классом идентификатора версии.

public class DecompressibleInputStream extends ObjectInputStream {

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


    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
        ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor
        Class localClass = Class.forName(resultClassDescriptor.getName()); // the class in the local JVM that this descriptor represents.
        if (localClass == null) {
            System.out.println("No local class for " + resultClassDescriptor.getName());
            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());
                System.out.println("Potentially Fatal Deserialization Operation. " + e);
                resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
            }
        }
        return resultClassDescriptor;
    }
}
1 голос
/ 22 марта 2012

вы можете найти серийный UID в формате HEX, если вы храните сериализованные данные в дБ, вы можете отредактировать и заменить старый UID новым серийным UID в формате HEX

1 голос
/ 28 апреля 2009

Вы должны иметь возможность решить проблему, переопределив ObjectInputStream.readClassDescriptor.

Использование XMLEncoder на самом деле не поможет с миграцией версий, так как правила совместимости практически одинаковы. В действительности, вероятно, следует сохранять объект в реляционной форме с помощью инструмента ORM.

Вероятно, разные serialVersionUID произошли из-за различных синтетических членов, генерируемых javac. Заголовок предупреждений и введите serialVersionUID in.

1 голос
/ 28 апреля 2009

Если у вас есть несколько версий класса, хранящихся в базе данных, может быть довольно сложно десериализовать и обновить их все до согласованного формата сериализации за один проход.

Если возможно, вы можете изменить таблицу с помощью столбца, чтобы указать, был ли обработан сериализованный объект. Затем выполните проходы по таблице для каждого serialVersionUID, где вы пытаетесь обработать любые объекты, с которыми еще не сталкивались. Вы можете поймать InvalidClassException и перейти к следующей записи, если ваш обновитель обнаружит сериализованный объект, который он не обрабатывает, запоминая номер версии, чтобы вы могли сделать еще один проход.

Это немного утомительно, но очень просто.

Сериализация Java имеет несколько замечательных функций для поддержки эволюции классов. Тем не менее, вы должны знать, что вы делаете. Может случиться так, что все объекты на самом деле имеют одинаковые данные, но не обращали внимания на поддержание идентификатора версии.

Вы можете продолжать использовать сериализацию после обновления всех объектов до одной и той же версии. Просто будьте осторожны, когда вы добавляете в класс новые поля, которые имеют смысл с их значениями по умолчанию (логические значения равны false, объекты равны нулю, целые числа равны нулю и т. Д.).

1 голос
/ 28 апреля 2009

Я могу что-то упустить, но это звучит , как будто вы пытаетесь сделать что-то более сложное, чем необходимо. Что будет, если:

(a) вы берете текущее определение класса (т.е. исходный код) и жестко кодируете его серийный UID к старому (или одному из старых), а затем используете это определение класса для десериализации сериализованных экземпляров?

(b) в байтовом потоке, который вы читаете, вы заменяете старые серийные UID на новые перед тем, как обернуть их ObjectInputStream?

ОК, просто чтобы уточнить (б). Так, например, если у меня есть маленький класс, как это:

  public static class MyClass implements Serializable {
    static final long serialVersionUID = 0x1122334455667788L;
    private int myField = 0xff;
  }

затем, когда данные сериализуются, это выглядит примерно так:

ACED000573720011746573742E546573 ’..sr..test.Tes
74244D79436C61737311223344556677 t$MyClass."3DUfw
880200014900076D794669656C647870 ?...I..myFieldxp
000000FF ...ÿ

Каждая строка состоит из 16 байтов, а каждый байт состоит из 2 шестнадцатеричных цифр. Если вы внимательно посмотрите, во второй строке, 9 байтов (18 цифр), вы увидите, что начинается серийный идентификатор версии (1122 ...). Таким образом, в наших данных здесь (ваши будут немного отличаться), смещение идентификатора серийной версии составляет 16 + 9 = 25 (или 0x19 в шестнадцатеричном формате). Поэтому, прежде чем я начну десериализацию, если я захочу изменить идентификатор этой серийной версии на что-то еще, мне нужно написать свой новый номер со смещением 25:

byte[] bytes = ... serialised data ...
ByteBuffer bb = ByteBuffer.wrap(bytes);
bb.putLong(25, newSerialVersionUID);

тогда я просто продолжаю как обычно:

ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes));
MyClass obj = (MyClass) oin.readObject();
0 голосов
/ 15 августа 2018

Возможно, что-то вроде хака, но может быть полезно кому-то: у меня была похожая проблема, которую я решил, продублировав класс-нарушитель и установив новый класс UID на 0L (например). Затем в коде, который выполняет сериализацию, я скопировал исходный объект в новый объект и сериализовал. Затем вы можете обновить свой код и код десериализации, чтобы использовать новый класс вместо старого. Это работает отлично, хотя вы застряли с новым именем класса. Однако вы можете повторить процесс, чтобы восстановить старое имя класса. В конце концов, у вас есть фиксированный UID на ваш выбор. Совет Я научился трудному пути: всегда устанавливайте свой собственный UID!

...