¿Hacer que el tiempo de ejecución de Java ignore serialVersionUID?
-
08-07-2019 - |
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?
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