2009-04-27 25 views
9

Mi cliente tiene una base de datos de Oracle y un objeto se conservó como un campo de blob a través de objOutStream.writeObject, el objeto ahora tiene un serialVersionUID diferente (aunque el objeto no tiene ningún cambio, puede ser diferente versión de la JVM) y cuando tratan de des-serializar se produce una excepción:Cómo deserializar un objeto persistido en un db ahora cuando el objeto tiene un serialVersionUID diferente

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

ellos no asignó un valor fijo para serialVersionUID desde el principio por lo que ahora que algo ha cambiado esa excepción se arroja. Ahora no quieren perder ningún dato, para hacerlo creo que lo mejor es leer los objetos, deserializarlos y persistirlos de nuevo a través de XMLEncoder para evitar futuros errores como el actual "clase incompatible".

Aparentemente hay 2 valores diferentes para el serialVersionUID persistente para ese objeto, así que quiero leer los datos, pruebe con un valor y si falla intente con el otro valor, para hacerlo intenté cambiar el serialVersionUID de la clase usando the ASM api. Pude cambiar el valor, pero el problema es cómo activar el cambio en la clase, por lo que cuando se deserializa, el objInpStr.readObject() toma mi versión modificada de la clase con mi serializedVersionUID específico. Hice una clase de prueba para simular el entorno real, tomo un objeto (que tiene como propiedad del objeto con diferentes serialVersionUID problema) el nombre del objeto es Reservation la propiedad es 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(); 
    } 

La prueba debe fallar con java.io.InvalidClassException la "clase local incompatibles" porque he cambiado el serialVersionUID después de que salvé el archivo y se utiliza una nueva para leer de archivo pero no falla lo que significa que el ObjectInputStream.readObject no es usando mi versión modificada del Reservation clase.

alguna idea? Gracias por adelantado.

!!!!!!!!!!!!! ACTUALIZACIÓN:

Ok, es posible redefinir el resultClassDescriptor para anular el serialVersionUID corriente, pero, algo extraño sucede, como he dicho antes parece que son 2 versiones de la clase persistente, objetos con serialVersionUID = -5239021592691549158L y otros con valor 8452040881660460728L este último valor es el generado si no especifico un valor para la clase local.

-Si no especifica un valor para el serialVersionUID entonces el valor por defecto (8452040881660460728L) se utiliza, pero no es posible des-serealize los objetos que tiene otro valor, se emite un error decir que una la propiedad es de otro tipo.

-Si especifico el valor -5239021592691549158L, las clases persisten con ese valor se deserializaron con éxito, pero no los otros, el mismo error de tipos.

esta es la traza de error:

Operación Deserialización potencialmente fatal. java.io.InvalidClassException: sobrescribir la versión de clase serializada no coincidente: local serialVersionUID = -5239021592691549158 stream serialVersionUID = 8452040881660460728 java.lang.ClassCastException: no se puede asignar la instancia de java.util.HashMap al campo com.posadas.ic.rules.common.commisionRules .CommissionResult.statusCode de tipo java.lang.String en instancia de com.posadas.ic.rules.common.commisionRules.CommissionResult

Cuando este error fue arrojado a la clase tenido el valor de -5239021592691549158, si el cambio el valor de 8452040881660460728 la clase es de-serializado con éxito, así que, ¿qué ocurre? ¿Por qué ese error intenta arrojar la clase equivocada?

Gracias

+0

Aquí es una pregunta similar http: //stackoverflow.com/questions/444909/java-modifying-serialversionuid-of-binary-serialized-object –

+0

Sí, leí esa pregunta antes, pero no encontré respuesta, Daniel dijo que había encontrado la manera de resolverla, pero no lo hizo. t detalla cómo, y parece ser lo que necesito, pero no encontré la manera de preguntarle: S –

Respuesta

1

me pueden faltar algo, pero sonidos como si estuviera tratando de hacer algo más complicado de lo necesario. ¿Qué sucede si:

(a) toma la definición de clase actual (es decir, el código fuente) y codifica su UID serie al anterior (o uno de los anteriores), luego use esa definición de clase para deserializar las instancias serializadas?

(b) en la secuencia de bytes que está leyendo, ¿reemplaza los UID de serie anteriores por el nuevo antes de ajustar el ObjectInputStream a su alrededor?

OK, solo para aclarar (b). Así, por ejemplo, si tengo un poco de clase como esta:

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

entonces cuando los datos están en serie, se ve algo como esto:

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

Cada línea es de 16 bytes, y cada byte es 2 dígitos hexadecimales Si observa detenidamente, en la segunda línea, 9 bytes (18 dígitos), verá que se inicia la identificación de la versión en serie (1122 ...). Por lo tanto, en nuestros datos aquí (los suyos difieren ligeramente), el desplazamiento de la ID de la versión en serie es 16 + 9 = 25 (o 0x19 en hexadecimal). Así que antes de empezar a deserialising, si quiero cambiar esta versión de ID de serie a otra cosa, entonces necesito escribir mi nuevo número en el offset 25:

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

entonces sólo procedo de forma normal:

ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes)); 
MyClass obj = (MyClass) oin.readObject(); 
+0

a) Es una prueba que ya hice, funciona con registros donde el objeto persistente tiene el mismo valor que el valor que especifico hardcoding el serialVersionUID, pero th Existen otros registros con un valor serialVersionUID diferente, esta solución significa que tendré que leer cada registro, intentar deserializar, si falla intente de nuevo más tarde (recompile con diferentes valores de versión de la versión en serie) y así ... hasta que todos los registros se migren (codificados como xml) pero hay más de 4 millones de registros: S y si hay más de 2 versiones de serialVersionID ... no una solución, tanques de cualquier manera –

+0

b) suena interesante, traté de cambiar la clase así que la instancia que el ObjectInputStream utiliza con el serialVersionID que especifico dinámicamente. pero ... cambiando el flujo de bytes encontrando el serialVersionUID y cambiándolo ... ¿cómo lo hago? Sería útil especificar el mismo valor que el objeto cambiado ... ¿algún ejemplo? gracias –

+0

Jorge, creo que está diciendo que si solo hay dos valores, podría procesar la mitad de la base de datos con el primer ID de serie, y luego cambiar la clase para procesar la segunda mitad. –

1

Si tiene múltiples versiones de la clase almacenadas en la base de datos, puede ser bastante complicado deserializarlas y actualizarlas a un formato de serialización consistente en una sola pasada.

Si es posible, puede modificar la tabla con una columna para indicar si el objeto serializado ya se ha procesado. A continuación, realice pases sobre la tabla para cada serialVersionUID, donde intenta procesar cualquier objeto que aún no se haya tratado. Puede tomar el InvalidClassException y pasar al siguiente registro si su actualizador encuentra un objeto serializado que no maneja, anotando el número de versión para que pueda hacer otro pase.

Esto es un poco tedioso, pero muy simple.

La serialización de Java tiene algunas características muy buenas para apoyar la evolución de las clases. Sin embargo, debes ser consciente de lo que estás haciendo. Es posible que todos los objetos tengan los mismos datos, pero no se prestó atención al mantenimiento de la ID de la versión.

Puede seguir utilizando la serialización una vez que obtenga todos los objetos actualizados en la misma versión. Solo tenga cuidado al agregar nuevos campos a la clase que tengan sentido con sus valores predeterminados (los booleanos son falsos, los objetos son nulos, las entradas son cero, etc.).

+0

, bueno, no suena mal, eso es algo que voy a intentar y puede ser la solución y fácil si solo hay 2 versiones, pero me gustaría saber cómo cambiar el valor de la identificación sobre la marcha cuando me parece que no es posible deserializar con el valor actual, de cualquier forma que intente guardar algo de cómo el valor de los elementos falló y repetir el proceso con ese valor, de cualquier manera con el indicador de columna evitaría procesar elementos ya deserializados, intentaré y le dejaré saber, gracias erickson –

1

Debería poder hackear el problema anulando ObjectInputStream.readClassDescriptor.

El uso de XMLEncoder en realidad no ayuda con la migración de la versión ya que las reglas de compatibilidad son muy similares. Realmente lo que probablemente debería estar haciendo es persistir en el objeto en una forma relacional con la ayuda de una herramienta ORM.

Probablemente los diferentes serialVersionUID ocurrieron debido a diferentes miembros sintéticos generados por javac. Dirija las advertencias y ponga serialVersionUID en.

+0

El tipo de cambios que mi cliente ha hecho es agregar nuevas propiedades, por ejemplo, en este caso no lo hicieron, el problema específico fue con la falta de serialVersionUID inicial, ¿realmente crees que habrá más problemas, incluso usando XMLEncoder si solo ¿Se agregaron propiedades ?, sería una buena solución usar una herramienta ORM, my be with hibernate persistiría en todo el objeto y crearía un esquema completo para resolverlo, pero no creo que el cliente le dé más tiempo: S, I Leeré el código ObjectInputStream para tratar de piratear para deserializar. gracias Tom. –

+0

La serialización de XMLEncoder y Java se comporta de manera muy similar al agregar un campo. Una gran ventaja de la serialización es que permite que se realicen formularios en serie personalizados (incluido el manejo de un formulario serializado heredado) dentro del objeto. Mira la fuente a Java.frijoles para ver algunos hacks feos para serializar clases estándar. –

15

Jorge Encontré una solución en http://forums.sun.com/thread.jspa?threadID=518416 que funciona.

Cree la siguiente clase en su proyecto. Siempre que cree un objeto de ObjectInputStream, use DecompressibleInputStream en su lugar y deserializa el objeto antiguo con la nueva versión de la clase Id.

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

esto realmente me ayudó. gracias. –

+0

muchas gracias! esto me ayudó a recuperar una tonelada de datos! – stantonk

+0

@Bhushan Bhangale, hola cuando intenté tu respuesta, java.io.StreamCorruptedException: código de tipo no válido: 00 viene en mi código – Tenacious

1

puede encontrar el UID serie en formato hexadecimal, si se almacenan los datos serializados en dB, puede editar y reemplazar el antiguo UID con el nuevo UID serie en formato hexadecimal

Cuestiones relacionadas