¡Salvado! El 70% de los programadores no conocen los detalles de serialización, claro

1. Implemente la interfaz serializable con cuidado

problema

El proceso de serialización consiste en "codificar un objeto en un flujo de bytes", el proceso opuesto se denomina "proceso de deserialización". Cuando se serializa un objeto, su codificación se puede transferir de una máquina virtual a otra y se puede guardar en el disco para su posterior deserialización . Ha habido un malentendido durante mucho tiempo de que para lograr la serialización, solo necesita implementar la interfaz serializable. De hecho, este método tiene muchos daños. La conveniencia de este método de serialización traerá costos de mantenimiento a largo plazo. ¿Cuáles son las precauciones con respecto a Serilizable?

responder

Desventajas de serializable

La implementación directa de la interfaz serializable tiene las siguientes desventajas:

Reducir la flexibilidad : si una clase implementa la interfaz serializable, su codificación de flujo de bytes también se convierte en parte de su API exportada. Una vez que esta clase se usa ampliamente, siempre debe admitir este método de serialización. Además, si se usa el Serializable predeterminado, los dominios de instancia privados y privados a nivel de paquete en esta clase pasarán a formar parte de la API exportada, que no se ajusta al principio de diseño del nivel de acceso mínimo del dominio. ** Además, si se cambia la estructura interna de la clase, el cliente intenta usar la versión anterior de la clase para la serialización y la nueva versión se usa para la deserialización, el programa saldrá mal. Si la clase serializada no muestra el identificador serialVersionUID especificado (UID de versión serial), el sistema automáticamente llamará a un proceso de cálculo complejo para generar el identificador basado en esta clase. Este identificador es un número de etiqueta generado según el nombre de la clase, el nombre de la interfaz y todos los nombres de miembros públicos y protegidos. Si cambia la estructura interna de la clase, como agregar un método, el UID de la versión de secuencia generada automáticamente también cambiará. Por lo tanto, si un número de versión no se declara explícitamente, la compatibilidad se romperá, lo que dará como resultado una InvalidClassException en tiempo de ejecución.

Es más fácil provocar errores y vulnerabilidades de seguridad :

Los objetos generales son creados por el constructor y la serialización también es un mecanismo de creación de objetos, y la deserialización también puede construir objetos. Dado que no hay un constructor explícito en el mecanismo de deserialización,

La deserialización debe garantizar:

La relación de restricción establecida por el constructor real no permite al atacante acceder a la información interna del objeto que se está construyendo . Dependiendo del mecanismo de deserialización predeterminado, es fácil destruir la relación de restricción del objeto y sufrir un acceso ilegal. La carga de prueba relacionada aumenta : cuando se modifica una clase serializable, es necesario marcar "Serializar una instancia en la nueva versión y deserializar en la versión anterior" y "Serializar una instancia en la versión anterior y revertirla en la nueva versión". Ya sea que la serialización sea normal o no, cuando la versión de lanzamiento aumenta, la cantidad de esta prueba es proporcional al producto del "número de clases serializables y el número de versión de lanzamiento". 2.Escenarios aplicables serializables Si es necesario agregar una clase a un marco, y el marco depende de la serialización para lograr la transmisión y persistencia del objeto, entonces es necesario que la clase implemente Seriablizable. Desde otro punto de vista, una clase pertenece a un componente. Si el componente principal implementa la interfaz Seriablizable, entonces la clase también necesita implementar la interfaz Seriablizable. Según la experiencia, las clases de valor como Date y BigInteger deben implementar Serializable, y la mayoría de las clases de colección también deben implementarse. 3.Escenarios serializables no aplicables  Las clases diseñadas para herencia deben implementar la interfaz serializable lo menos posible, y la interfaz de usuario no debe heredar la interfaz serializable tanto como sea posible , porque la subclase o clase de implementación también conlleva el riesgo de serialización. En la mayoría de los casos, se debe seguir este principio. Circunstancias muy especiales pueden romper este principio. Por ejemplo, las clases que implementan la interfaz serializable incluyen la clase Throwable (las excepciones se pueden pasar del servidor al cliente), la clase Component (GUI se puede enviar, Guardar y restaurar), clase abstracta HttpServlet (la sesión de sesión se puede almacenar en caché); las clases internas no deben implementar Serializable , las clases internas necesitan guardar referencias a instancias de clases externas y guardar los valores de variables locales de ámbitos externos. No se sabe con certeza cómo estos campos corresponden a la definición de clase. Por lo tanto, la forma de serialización predeterminada de la clase interna no está clara.

En conclusión

En resumen, no equipare la serialización con una simple implementación de la interfaz Serilizable, debe considerar los escenarios de aplicación de Seriablizable y las precauciones mencionadas anteriormente.

Considere usar un formulario de serialización personalizado

problema

Diseñar la forma de serialización de una clase es tan importante como diseñar la API de la clase, así que no use el comportamiento de serialización predeterminado antes de considerar seriamente si la forma de serialización predeterminada es apropiada . Antes de tomar una decisión, debe examinar esta forma de codificación desde múltiples perspectivas de flexibilidad, rendimiento y corrección . En términos generales, puede aceptar el formulario de serialización predeterminado solo cuando el formulario de serialización personalizado que diseñe usted mismo sea básicamente el mismo que el formulario predeterminado. ¿Cuáles son las precauciones para elegir el método de serialización adecuado?

responder

El formulario de serialización predeterminado describe los datos contenidos en el objeto y los datos internos de cada uno de los otros objetos a los que se puede acceder desde este objeto, es decir, describe completamente la estructura de topología de todos los objetos conectados. Para un objeto, la forma de serialización ideal solo debe contener los datos lógicos representados por el objeto, y los datos lógicos y la representación física deben ser independientes entre sí. En otras palabras, si la representación física de un objeto es equivalente a su contenido lógico, la forma de serialización predeterminada es adecuada. Existe un ejemplo de clase pública Name implementa Serializable {private final String lastName; private final String firstName; private final String middleName; ... ...}

Desde un punto de vista lógico, la clase Name se puede representar simplemente con tres atributos, lastName, firstName y middleName, es decir, estos tres atributos pueden reflejar con precisión su contenido lógico. Por lo tanto, en este caso, se puede utilizar el formulario de serialización predeterminado, y también se requieren la detección de validez de parámetros y la copia protectora en readObject. Usando el formulario de serialización predeterminado, cuando uno o más campos de campo están marcados como transitorios, si se va a realizar la deserialización, estos campos de campo se inicializarán a sus valores predeterminados de tipo , como el campo de referencia del objeto se establece en nulo, el valor es básicamente El valor predeterminado del dominio es 0 y el valor predeterminado del dominio booleano es falso. Si estos valores no pueden ser modificados por ningún campo transitorio, debe proporcionar un método readObject. Primero llama a defaultReadObject y luego restaura estos campos transitorios a sus valores iniciales anteriores; de manera similar, en el proceso de serialización, se omitirán los campos de instancia modificados transitorios. En el proceso de serialización, la máquina virtual intenta llamar a la clase de objeto En writeObject () y readObject (), puede implementar su propia lógica de serialización en los métodos readObject y writeObject. Incluso si no se implementa una lógica específica, debe llamar a los métodos predeterminados ObjectOutputStream.defaultWriteObject () y ObjectInputStream.defaultReadObject (), para que pueda garantizar la compatibilidad hacia adelante o hacia atrás; no importa qué forma de serialización elija, debe ser Cada clase serializable que escribe declara un UID de versión serial explícito. Esto puede evitar que el UID de la versión en serie se convierta en una fuente potencial de incompatibilidad y también traerá un pequeño beneficio de rendimiento, ya que no es necesario calcular el UID de la versión en serie.

En conclusión

Cuando decida diseñar una clase para que sea serializable, debe considerar cuidadosamente qué forma de serialización debe usarse. Solo cuando el formulario de serialización predeterminado puede describir razonablemente el estado lógico del objeto, se puede utilizar el formulario de serialización predeterminado. De lo contrario, es necesario diseñar un formulario de serialización personalizado, a través del cual se pueda describir razonablemente el estado del objeto.

Utilice el método readObject con precaución

problema

Para que el programa sea más seguro y confiable, es necesario hacer una copia protectora en el constructor y método de acceso para el dominio de la variable, por ejemplo, el siguiente código: public final static class Period {private final Date start; private final Date end; public Period (Date start , Fecha de fin) {this.start = new Date (start.getTime ()); this.end = new Date (end.getTime ()); if (this.start.compareTo (this.end)> 0) {throw newIllegalArgumentException (start + "after" + end);}} public Date getStart () {return newDate (start.getTime ());} public Date getEnd () {return new Date (end.getTime ());}}

Pero si esta clase está serializada, puede parecer que esta clase no satisface la relación de restricción de inicio y fin. Entonces, ¿cómo debemos asegurarnos de que la relación de restricción de clave del objeto también se pueda garantizar durante la serialización?

responder

Además de construir objetos por constructores, la deserialización también es una forma de construir objetos, por lo tanto, las comprobaciones de validez de los parámetros y la copia protectora también son necesarias al construir objetos . Por lo tanto, el método readObject también debe garantizar que las restricciones clave de Period permanezcan sin cambios y mantengan su inmutabilidad:

  private void readObject(ObjectInputStream s)
  throws IOException, ClassNotFoundException {
      s.defaultReadObject();
      // Defensively copy our mutable components
      start = new Date(start.getTime());
      end = new Date(end.getTime());
      // Check that our invariants are satisfied
      if (start.compareTo(end) > 0)
          throw new InvalidObjectException(start +" after "+ end);
      }
  }

Y debe tenerse en cuenta que la copia protectora está antes de la verificación de validez del parámetro y el método de clonación no se puede utilizar para copiar el objeto.

En conclusión

Con todo, cada vez que escriba un método readObject, piense así:

Estás escribiendo un constructor público. No importa qué flujo de bytes se le pase, debe producir una instancia válida. La siguiente experiencia ayuda a escribir un método readObject más sólido: El dominio de referencia del objeto debe mantenerse privado y cada objeto en estos dominios debe estar protegido contra copia. Los componentes mutables de clases inmutables entran en esta categoría; para cualquier restricción, si la comprobación falla, se lanzará una InvalidObjectException. Estas acciones de verificación deben seguir todas las copias protectoras; si el gráfico de objeto completo debe validarse después de ser deserializado, se debe usar la interfaz ObjectInputValidation; el método reemplazable no debe llamarse en el método readObject, ya sea indirectamente o indirectamente Camino directo

Utilice la enumeración para implementar singleton

problema

Para Singleton, la forma más sencilla es:

public class Elvis {public static final Elvis INSTANCE = new Elvis (); private Elvis () {...} public void leaveTheBuilding () {...}}  Si la clase está serializada, independientemente del método de serialización predeterminado O use un método de serialización personalizado, o realice el llamado procesamiento en el método readObject, esta clase no será un singleton. Entonces, ¿cómo lograr este tipo de singleton que debe ser serializable?

responder

Para satisfacer el singleton serializable, hay dos formas:

Utilice el método readResolve :

La función readResolve le permite reemplazar otra instancia con una instancia creada por readObject. Para un objeto que se está deserializando, si su clase define un método readResolve y tiene la declaración correcta, luego de la deserialización, se llamará al método readResolve en el objeto recién creado. Luego, se devolverá la referencia de objeto devuelta por este método, reemplazando el objeto recién creado. Por lo tanto, cada vez que deserializa, puede devolver el objeto de instancia anterior en el método readResolve, por lo que puede asegurarse de que solo habrá un objeto después de varias deserializaciones. El código de muestra es:  // readResolve, por ejemplo, control, ¡puedes hacerlo mejor!  Private Object readResolve () { // Devuelve el único Elvis verdadero y deja que el recolector de basura // se encargue del imitador de Elvis.  Return INSTANCE;} El método ignora el objeto que se deserializa y solo devuelve la instancia especial de Elvis creada cuando se inicializó la clase. De hecho, si confía en readResolve para el control de instancia, todos los dominios de instancia con tipos de referencia de objeto deben declararse como transitorios . De lo contrario, el singleton implementado por el método readResolve también será atacado.  

Utilice la enumeración para lograr :

Las enumeraciones se pueden usar para implementar singleton serializables. Esta seguridad está garantizada por JVM, y el código es muy conciso, y el dominio de instancia no necesita ser modificado con transitorio:  // Enum singleton-el enfoque preferido  public enumElvis {INSTANCE; private String [] favoriteSongs = {"Hound Dog", "Heartbreak Hotel"}; public void printFavorites () {System.out.println (Arrays.toString (favoriteSongs));}}

En conclusión

La forma más sencilla y segura de lograr la serialización es utilizar la forma de enumeración, que debe utilizarse tanto como sea posible. Si lo implementa readResolve, se puede garantizar que todos los campos de instancia de esta clase sean de tipo básico o transitorio.

Autor: Escuche ___

Enlace: https://juejin.im/post/6883434777416990728

Fuente: Nuggets

Supongo que te gusta

Origin blog.csdn.net/GYHYCX/article/details/109098162
Recomendado
Clasificación