Как десериализовать объект сохранился в БД сейчас, когда объект имеет другой serialVersionUID
У моего клиента есть база данных Oracle, и объект был сохранен как поле большого двоичного объекта через 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
учебный класс.
Есть идеи? Заранее спасибо.
!!!!!!!!!!!!!ОБНОВИТЬ:
Хорошо, можно переопределить resultClassDescriptor для переопределения потока serialVersionUID, но происходит нечто странное, как я уже говорил, до того, как кажется, что сохраняются 2 версии класса, объекты с serialVersionUID = -5239021592691549158L и другие со значением 8452040881660460728L это последнее Значение генерируется, если я не указываю значение для локального класса.
-Если я не указываю значение для serialVersionUID, то используется значение по умолчанию (8452040881660460728L), но невозможно десереализовать объекты, которые имеют другое значение, выдается сообщение о том, что свойство другого объекта тип.
-Если я укажу значение -5239021592691549158L, классы сохранятся с этим значением, успешно десериализованные, но не другие, та же ошибка типов.
это трассировка ошибки:
Потенциально фатальная операция десериализации. java.io.InvalidClassException: Переопределяющее несоответствие версии сериализованного класса: local serialVersionUID = -5239021592691549158 stream serialVersionUID = 8452040881660460728 java.lang.ClassCastException: не удается назначить экземпляр java.util.HashMap.commonles.R ессылки. com.CommissionResult.statusCode типа java.lang.String в экземпляре com.posadas.ic.rules.common.commisionRules.CommissionResult
Когда эта ошибка была выдана, класс имел значение -5239021592691549158, если изменить значение на 8452040881660460728, класс успешно десериализовался, так что же происходит? почему та ошибка, которая пытается привести к неправильному классу?
Спасибо
6 ответов
Хорхе Я нашел одно решение на 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;
}
}
Вы можете найти серийный UID в формате HEX, если вы храните сериализованные данные в БД, вы можете отредактировать и заменить старый UID новым серийным UID в формате HEX.
Если у вас есть несколько версий класса, хранящихся в базе данных, может быть довольно сложно десериализовать и обновить их все до согласованного формата сериализации за один проход.
Если возможно, вы можете изменить таблицу с помощью столбца, чтобы указать, был ли обработан сериализованный объект. Затем делайте проходы по столу для каждого serialVersionUID
где вы пытаетесь обработать любые объекты, которые еще не были обработаны. Вы можете поймать InvalidClassException
и перейдите к следующей записи, если ваш модуль обновления обнаружит сериализованный объект, который он не обрабатывает, запоминая номер версии, чтобы вы могли сделать еще один проход.
Это немного утомительно, но очень просто.
Сериализация Java имеет несколько очень полезных функций для поддержки эволюции классов. Тем не менее, вы должны знать, что вы делаете. Может случиться так, что все объекты на самом деле имеют одинаковые данные, но не уделялось внимания поддержанию идентификатора версии.
Вы можете продолжить использовать сериализацию, как только все объекты обновятся до одной и той же версии. Просто будьте осторожны, когда вы добавляете в класс новые поля, которые имеют смысл с их значениями по умолчанию (булевы значения равны false, объекты равны нулю, целые числа равны нулю и т. Д.).
Вы должны быть в состоянии взломать проблему, переопределив ObjectInputStream.readClassDescriptor
,
С помощью XMLEncoder
на самом деле не поможет с миграцией версий, так как правила совместимости практически одинаковы. На самом деле, вероятно, следует сохранить объект в реляционной форме с помощью инструмента ORM.
Наверное, разные serialVersionUID
Это произошло из-за различных синтетических членов, генерируемых javac. Возглавьте предупреждения и поставьте serialVersionUID
в.
Я могу что-то упустить, но похоже, что вы пытаетесь сделать что-то более сложное, чем необходимо. Что произойдет, если:
(а) вы берете текущее определение класса (т.е. исходный код) и жестко кодируете его серийный UID к старому (или одному из старых), а затем используете это определение класса для десериализации сериализованных экземпляров?
(б) в байтовом потоке, который вы читаете, вы заменяете старые последовательные идентификаторы 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();
Возможно, что-то вроде хака, но может быть полезно кому-то: у меня была похожая проблема, которую я решил, продублировав класс-нарушитель и установив UID нового класса на 0L (например). Затем в коде, который выполняет сериализацию, я скопировал исходный объект в новый объект и выполнил сериализацию. Затем вы можете обновить свой код и код десериализации, чтобы использовать новый класс вместо старого. Это работает отлично, хотя вы застряли с новым именем класса. Однако вы можете повторить процесс, чтобы восстановить старое имя класса. В конце концов, у вас есть фиксированный UID по вашему выбору. Совет Я научился трудному пути: всегда устанавливайте свой собственный UID!