Uso de readClassDescriptor () y tal vez resolveClass () para permitir el control de versiones de serialization

Estoy investigando diferentes opciones en el mecanismo de Serialización de Java para permitir flexibilidad en nuestras estructuras de class para el almacenamiento tolerante a la versión (y abogando por un mecanismo diferente, no es necesario que me lo diga).

Por ejemplo, el mecanismo de serialization pnetworkingeterminado puede manejar tanto la adición como la eliminación de campos, si solo se requiere compatibilidad con versiones anteriores.

Sin embargo, cambiar el nombre de una class o moverlo a un package diferente resultó ser mucho más difícil. Encontré en esta pregunta que era capaz de hacer un simple cambio de nombre de class y / o mover package, al crear una subclass de ObjectInputStream y anular readClassDescriptor ():

if (resultClassDescriptor.getName().equals("package.OldClass")) resultClassDescriptor = ObjectStreamClass.lookup(newpackage.NewClass.class); 

Eso está bien para cambiar el nombre simple. Pero si luego intenta agregar o eliminar un campo, obtendrá una java.io.StreamCorruptedException. Peor aún, esto sucede incluso si se ha agregado o eliminado un campo, y luego cambia el nombre de la class, lo que podría causar problemas con múltiples desarrolladores o múltiples loggings.

Basándome en algunas lecturas que había hecho, experimenté un poco con la idea de replace correctamente el nombre de la class nueva, pero no cargar la class anterior y bombardear los cambios de campo. Pero esto proviene de una comprensión muy vaga de algunos de los detalles del mecanismo de serialization, y no estoy seguro si estoy ladrando hasta el tree correcto.

Entonces 2 preguntas precisas:

  1. ¿Por qué se produce un error al restablecer el nombre de la class utilizando readClassDescriptor () que causa la deserialization en los cambios de class normales y compatibles?
  2. ¿Hay alguna manera de usar resolveClass () u otro mecanismo para evitar esto y permitir que las classs evolucionen (agreguen y eliminen campos) y se renombren / reempackagen?

Busqué y no pude encontrar una pregunta equivalente en SO. De todos modos, indíqueme si existe tal pregunta, pero lea la pregunta con cuidado para no cerrarme a less que otra pregunta realmente responda mi pregunta precisa.

Tuve los mismos problemas de flexibilidad que tú y encontré el path. Así que aquí mi versión de readClassDescriptor ()

  static class HackedObjectInputStream extends ObjectInputStream { /** * Migration table. Holds old to new classes representation. */ private static final Map<String, Class<?>> MIGRATION_MAP = new HashMap<String, Class<?>>(); static { MIGRATION_MAP.put("DBOBHandler", com.foo.valueobjects.BoardHandler.class); MIGRATION_MAP.put("DBEndHandler", com.foo.valueobjects.EndHandler.class); MIGRATION_MAP.put("DBStartHandler", com.foo.valueobjects.StartHandler.class); } /** * Constructor. * @param stream input stream * @throws IOException if io error */ public HackedObjectInputStream(final InputStream stream) throws IOException { super(stream); } @Override protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); for (final String oldName : MIGRATION_MAP.keySet()) { if (resultClassDescriptor.getName().equals(oldName)) { String replacement = MIGRATION_MAP.get(oldName).getName(); try { Field f = resultClassDescriptor.getClass().getDeclanetworkingField("name"); f.setAccessible(true); f.set(resultClassDescriptor, replacement); } catch (Exception e) { LOGGER.severe("Error while replacing class name." + e.getMessage()); } } } return resultClassDescriptor; } 

El problema es que se supone que readClassDescriptor le dice a ObjectInputStream cómo leer los datos que se encuentran actualmente en la secuencia que está leyendo. si miras dentro de una secuencia de datos serializada, verás que no solo almacena los datos, sino también muchos metadatos sobre exactamente qué campos están presentes. esto es lo que permite que la serialization maneje adiciones / eliminaciones de campo simples. sin embargo, cuando anula ese método y descarta la información devuelta de la transmisión, está descartando la información sobre qué campos están en los datos serializados.

Creo que la solución al problema sería tomar el valor devuelto por super.readClassDescriptor () y crear un nuevo descriptor de class que devuelva el nuevo nombre de class, pero devuelve la información del descriptor anterior. (aunque, al mirar ObjectStreamField, puede ser más complicado que eso, pero esa es la idea general).

Esto es para lo que writeReplace () y readResolve () son. Lo estás haciendo mucho más complicado de lo que realmente es. Tenga en count que puede definir estos methods en los dos objects en cuestión o en subclasss de las classs de flujo de objects.

No he jugado con los descriptores de class lo suficiente, pero si su problema es cambiar el nombre y volver a empaquetar, hay una solución mucho más fácil para eso. Simplemente podría editar su file de datos serializados con un editor de text y simplemente replace sus nombres anteriores por otros nuevos. está ahí en una forma legible por humanos. por ejemplo, supongamos que tenemos este OldClass colocado dentro de oldpackage y que contiene un oldField , como este:

 package oldpackage; import java.io.Serializable; public class OldClass implements Serializable { int oldField; } 

Ahora cuando serializamos una instancia de esta class y obtenemos algo como esto:

 ¬í sr oldpackage.OldClasstqŽÇ§Üï I oldFieldxp 

Ahora, si queremos cambiar el nombre de la class a NewClass y colocar el newpackage y cambiar el nombre de su campo a newField , simplemente le cambio el nombre al file, así:

 ¬í sr newpackage.NewClasstqŽÇ§Üï I newFieldxp 

y define el serialVersionUID apropiado para la nueva class.

Eso es todo. No se requiere extender y anular.

Intereting Posts