1. Información general
Kryo es un marco de serialización de Java que se centra en la velocidad, la eficiencia y una API fácil de usar.
En este artículo, exploraremos las características clave del marco de Kryo e implementaremos ejemplos para mostrar sus capacidades.
2. Dependencia de Maven
Lo primero que debemos hacer es agregar la dependencia kryo a nuestro pom.xml :
com.esotericsoftware kryo 4.0.1
La última versión de este artefacto se puede encontrar en Maven Central.
3. Conceptos básicos de Kryo
Comencemos por ver cómo funciona Kryo y cómo podemos serializar y deserializar objetos con él.
3.1. Introducción
El marco proporciona la clase Kryo como el principal punto de entrada para toda su funcionalidad.
Esta clase organiza el proceso de serialización y asigna clases a instancias de serializador que manejan los detalles de convertir el gráfico de un objeto en una representación de bytes.
Una vez que los bytes están listos, se escriben en una secuencia utilizando un objeto de salida . De esta forma, pueden almacenarse en un archivo, una base de datos o transmitirse a través de la red.
Más tarde, cuando se necesita el objeto, se utiliza una instancia de entrada para leer esos bytes y decodificarlos en objetos Java.
3.2. Serializar objetos
Antes de sumergirnos en los ejemplos, primero creemos un método de utilidad para inicializar algunas variables que usaremos para cada caso de prueba en este artículo:
@Before public void init() { kryo = new Kryo(); output = new Output(new FileOutputStream("file.dat")); input = new Input(new FileInputStream("file.dat")); }
Ahora, podemos ver lo fácil que es escribir y leer un objeto usando Kryo:
@Test public void givenObject_whenSerializing_thenReadCorrectly() { Object someObject = "Some string"; kryo.writeClassAndObject(output, someObject); output.close(); Object theObject = kryo.readClassAndObject(input); input.close(); assertEquals(theObject, "Some string"); }
Observe la llamada al método close () . Esto es necesario ya que las clases Output y Input heredan de OutputStream y InputStream respectivamente.
Serializar varios objetos es igualmente sencillo:
@Test public void givenObjects_whenSerializing_thenReadCorrectly() { String someString = "Multiple Objects"; Date someDate = new Date(915170400000L); kryo.writeObject(output, someString); kryo.writeObject(output, someDate); output.close(); String readString = kryo.readObject(input, String.class); Date readDate = kryo.readObject(input, Date.class); input.close(); assertEquals(readString, "Multiple Objects"); assertEquals(readDate.getTime(), 915170400000L); }
Observe que estamos pasando la clase apropiada al método readObject () , esto hace que nuestro código sea libre de conversión .
4. Serializadores
En esta sección, mostraremos qué serializadores ya están disponibles y luego crearemos los nuestros.
4.1. Serializadores predeterminados
Cuando Kryo serializa un objeto, crea una instancia de una clase Serializer previamente registrada para hacer la conversión a bytes. Estos se denominan serializadores predeterminados y se pueden usar sin ninguna configuración de nuestra parte.
La biblioteca ya proporciona varios serializadores de este tipo que procesan primitivas, listas, mapas, enumeraciones, etc. Si no se encuentra ningún serializador para una clase determinada, se utiliza un FieldSerializer , que puede manejar casi cualquier tipo de objeto.
Veamos cómo se ve esto. Primero, creemos una clase Person :
public class Person { private String name = "John Doe"; private int age = 18; private Date birthDate = new Date(933191282821L); // standard constructors, getters, and setters }
Ahora, escriba un objeto de esta clase y luego leámoslo:
@Test public void givenPerson_whenSerializing_thenReadCorrectly() { Person person = new Person(); kryo.writeObject(output, person); output.close(); Person readPerson = kryo.readObject(input, Person.class); input.close(); assertEquals(readPerson.getName(), "John Doe"); }
Tenga en cuenta que no tuvimos que especificar nada para serializar un objeto Person ya que un FieldSerializer se crea automáticamente para nosotros.
4.2. Serializadores personalizados
Si necesitamos más control sobre el proceso de serialización, tenemos dos opciones; podemos escribir nuestra propia clase de serializador y registrarla con Kryo o dejar que la clase maneje la serialización por sí misma.
Para demostrar la primera opción, creemos una clase que amplíe Serializer :
public class PersonSerializer extends Serializer { public void write(Kryo kryo, Output output, Person object) { output.writeString(object.getName()); output.writeLong(object.getBirthDate().getTime()); } public Person read(Kryo kryo, Input input, Class type) { Person person = new Person(); person.setName(input.readString()); long birthDate = input.readLong(); person.setBirthDate(new Date(birthDate)); person.setAge(calculateAge(birthDate)); return person; } private int calculateAge(long birthDate) { // Some custom logic return 18; } }
Ahora, pongámoslo a prueba:
@Test public void givenPerson_whenUsingCustomSerializer_thenReadCorrectly() { Person person = new Person(); person.setAge(0); kryo.register(Person.class, new PersonSerializer()); kryo.writeObject(output, person); output.close(); Person readPerson = kryo.readObject(input, Person.class); input.close(); assertEquals(readPerson.getName(), "John Doe"); assertEquals(readPerson.getAge(), 18); }
Observe que el campo de edad es igual a 18, aunque lo establecimos previamente en 0.
También podemos usar la anotación @DefaultSerializer para que Kryo sepa que queremos usar PersonSerializer cada vez que necesite manejar un objeto Person . Esto ayuda a evitar la llamada al método register () :
@DefaultSerializer(PersonSerializer.class) public class Person implements KryoSerializable { // ... }
Para la segunda opción, modifiquemos nuestra clase Person para extender la interfaz KryoSerializable :
public class Person implements KryoSerializable { // ... public void write(Kryo kryo, Output output) { output.writeString(name); // ... } public void read(Kryo kryo, Input input) { name = input.readString(); // ... } }
Dado que el caso de prueba para esta opción es igual a uno anterior, no se incluye aquí. Sin embargo, puede encontrarlo en el código fuente de este artículo.
4.3. Serializador de Java
En casos esporádicos, Kryo no podrá serializar una clase. Si esto sucede, y escribir un serializador personalizado no es una opción, podemos usar el mecanismo de serialización estándar de Java usando un JavaSerializer . Esto requiere que la clase implemente la interfaz serializable como de costumbre.
Aquí hay un ejemplo que usa el serializador mencionado anteriormente:
public class ComplexObject implements Serializable { private String name = "Bael"; // standard getters and setters }
@Test public void givenJavaSerializable_whenSerializing_thenReadCorrectly() { ComplexClass complexObject = new ComplexClass(); kryo.register(ComplexClass.class, new JavaSerializer()); kryo.writeObject(output, complexObject); output.close(); ComplexClass readComplexObject = kryo.readObject(input, ComplexClass.class); input.close(); assertEquals(readComplexObject.getName(), "Bael"); }
5. Conclusión
En este tutorial, exploramos las características más notables de la biblioteca Kryo.
Serializamos varios objetos simples y usamos la clase FieldSerializer para tratar con uno personalizado. También creamos un serializador personalizado y demostramos cómo recurrir al mecanismo de serialización estándar de Java si es necesario.
Como siempre, el código fuente completo de este artículo se puede encontrar en Github.