Pregunta

Tengo que trabajar con una gran cantidad de clases compiladas de Java que no especificaron explícitamente un serialVersionUID. Debido a que sus compiladores generaron arbitrariamente sus UID, muchas de las clases que necesitan ser serializadas y deserializadas terminan causando excepciones, a pesar de que las definiciones de clase reales coinciden. (Esto es todo comportamiento esperado, por supuesto).

No es práctico para mí regresar y corregir todo este código de terceros.

Por lo tanto, mi pregunta es: ¿Hay alguna forma de hacer que el tiempo de ejecución de Java ignore las diferencias en serialVersionUID y solo no se deserialice cuando haya diferencias reales en la estructura?

¿Fue útil?

Solución

Si tiene acceso a la base de código, puede usar la tarea SerialVer para Ant para insertar y modifique el serialVersionUID en el código fuente de una clase serializable y solucione el problema de una vez por todas.

Si no puede, o si esto no es una opción (por ejemplo, si ya ha serializado algunos objetos que necesita deserializar), una solución sería extender ObjectInputStream . Aumente su comportamiento para comparar el serialVersionUID del descriptor de flujo con el serialVersionUID de la clase en la JVM local que representa este descriptor y usar el descriptor de clase local en caso de falta de coincidencia . Luego, solo use esta clase personalizada para la deserialización. Algo como esto (créditos para este mensaje ):

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

Otros consejos

¿Qué tan poco práctico es arreglar esto? Si tiene la fuente y puede reconstruir, ¿no puede simplemente ejecutar una secuencia de comandos sobre la base de código completa para insertar una

private long serialVersionUID = 1L;

en todas partes?

¿Usa CGLIB para insertarlos en las clases binarias?

Los errores de serialización en tiempo de ejecución le indican explícitamente cuál es la identificación que se espera que sea. Simplemente cambie sus clases para declararlas como ID y todo estará bien. Esto implica que realice cambios, pero no creo que esto pueda evitarse

Posiblemente podría usar Aspectj para 'introducir' el campo en cada clase serializable a medida que se carga. Primero introduciría una interfaz de marcador en cada clase usando el paquete y luego introduciría el campo usando un Hash del archivo de clase para el 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;
   }
}

Deberá agregar el Aspectj load time weaver agent a la VM para que pueda tejer los consejos en sus clases de terceros existentes. Aunque es divertido una vez que configuras Aspectj, es notable la cantidad de usos que le darás.

HTH

ste

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top