Comprendre le sérialisable de Java tout en exécutant l'application

Comprenez-vous la signification de Serializable?

Lorsque vous créez une application Web en Java, vous pouvez créer la classe JavaBeans suivante.

JavaBeans.java


import java.io.Serializable;

public class JavaBeans implements Serializable {

    private static final long serialVersionUID = 1L;

    private String aaa = null;

    public String getAaa() {
        return aaa;
    }

    public void setAaa(String aaa) {
        this.aaa = aaa;
    }
}

Comprenez-vous ce que signifient ces ʻimplements Serializable et serialVersionUID = 1L; `? Cet article a été écrit pour les personnes qui disent "Je ne sais pas!" Les gens qui disent "je comprends!" N'ont pas besoin de lire cet article.

D'abord du concept

La sortie d'une instance Java sous forme de tableau d'octets s'appelle Serialize, et vice versa s'appelle Deserialize. Comme vous pouvez le voir dans la figure ci-dessous, l'enregistrement d'une instance dans un fichier, une mémoire, une base de données, etc. s'appelle Serialize.

image.png La figure est extraite de cet article

Donc déclarer ʻimplements Serializable` signifie déclarer "Cette instance peut être sauvegardée sur le disque, etc.!".

J'ai une application web que j'ai faite un peu personnellement, donc je vais l'expérimenter.

TestServlet.java


public class TestServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    //・ ・ ・
}

Le TestServlet ci-dessus étendHttpServlet, HttpServlet étend GenericServlet et GenericServlet implémenteSerializable, donc TestServlet estSerializable.

image.png

Démarrez le serveur (Tomcat) pour cette application Web. Il n'y a encore rien sous le dossier localhost de la destination de déploiement.

image.png

Après cela, ouvrez le navigateur ou faites quelque chose pour que TestServlet fonctionne et arrêtez le serveur. Ensuite, un fichier mystérieux appelé SESSIONS.ser sera créé directement sous le dossier localhost de la destination de déploiement.

image.png

Redémarrez le serveur. Puis «SESSIONS.ser» disparaît.

image.png

SESSIONS.ser est une information de session sérialisée par Tomcat. Grâce à cela, les informations de session ne sont pas perdues même si le serveur est redémarré, et les informations avant le redémarrage peuvent être restaurées (désérialiser) en y accédant après le redémarrage. Inversement, pour cette spécification, les objets stockés dans la session doivent implémenter Serializable. Cela semble pratique à première vue, mais savez-vous ce qui se passe si le programme est modifié avant et après le redémarrage?

Faisons en fait Serialize / Deserialize

Créez une application avec la configuration ci-dessous et faites-la sérialiser / désérialiser.

image.png

Tout d'abord, celui qui sérialise. Créez «SampleBean.java» et «WriteObject.java» pour le sérialiser dans n'importe quel dossier. WriteObject.java crée une instance SampleBean, définit deux propriétés sur 100 et la sérialise. Créez sample.ser en exécutant WriteObject. sample.ser est une version sérialisée de l'instance SampleBean.

image.png

SampleBean.java


import java.io.Serializable;

public class SampleBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private double propertyD;
    private int propertyE;

    public double getPropertyD() {
        return propertyD;
    }

    public void setPropertyD(double propertyD) {
        this.propertyD = propertyD;
    }

    public int getPropertyE() {
        return propertyE;
    }

    public void setPropertyE(int propertyE) {
        this.propertyE = propertyE;
    }
}

WriteObject.java


import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class WriteObject {
    public static void main (String args[]) {
        SampleBean sample = new SampleBean();
        sample.setPropertyD(100);
        sample.setPropertyE(100);

        try {
            FileOutputStream fout = new FileOutputStream("sample.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fout);
            oos.writeObject(sample);
            oos.close();
            System.out.println("Done");
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}

Vient ensuite la personne qui désérialise. Créez une classe (JspTest.java) qui lit le sample.ser généré ci-dessus. La classe SampleBean utilisée dans JspTest.java est la même que la précédente, copiée dans un autre dossier.

JspTest.java


import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/JspTest")
public class JspTest extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        SampleBean sample = null;
        List<SampleBean> list = new ArrayList<>();
        try {
            FileInputStream fin = new FileInputStream("c:\\sample.ser");
            ObjectInputStream ois = new ObjectInputStream(fin);
            sample = (SampleBean) ois.readObject();
            ois.close();
            list.add(sample);
        } catch(Exception ex) {
            ex.printStackTrace();
        }

        request.setAttribute("samplelist", list);
        String path = "/WEB-INF/jsp/jsptest.jsp";
        RequestDispatcher disp = request.getRequestDispatcher(path);
        disp.forward(request, response);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

Jsp pour l'afficher

jsptest.jsp


<%@ page language="java" contentType="text/html;charset=Windows-31J"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page isELIgnored="false" %>

<html><body>
<table border="1">
  <thead>
    <tr>
      <th>Propriété D</th>
      <th>Propriété E</th>
    </tr>
  </thead>
  <tbody>
    <c:forEach items="${samplelist}" var="item">
      <tr>
        <td><c:out value="${item.propertyD}" /></td>
        <td><c:out value="${item.propertyE}" /></td>
      </tr>
    </c:forEach>
  <tbody>
</table>
</body></html>

Résultat de l'exécution (affichage du navigateur) image.png

J'ai pu désérialiser l'instance sérialisée et l'afficher dans le navigateur. C'est Serialize / Deserialize.

Qu'est-ce que serialVersionUID?

Vient ensuite serialVersionUID. Si vous écrivez implements Serializable dans votre classe et ne déclarez pas serialVersionUID, vous obtiendrez l'avertissement La classe sérialisable XXXX ne déclare pas un champ serialVersionUID final statique de type long. serialVersionUID est la version de l'instance. Je ne pense pas que ce soit très clair, alors je vais faire une petite expérience pour le comprendre plus concrètement. Faisons le SampleBean.java utilisé lors de la sérialisation plus tôt et le serialVersionUID de SampleBean.java utilisé lors de la désérialisation de l'incompatibilité. Remplacez SampleBean.java (celui qui désérialise) utilisé par JspTest.java par serialVersionUID = 2L;. Le SampleBean.java sérialisé reste serialVersionUID = 1L;. Après avoir effectué la modification, si vous mettez à jour le navigateur avec F5, l'erreur suivante sera générée.

contenu de l'erreur


java.io.InvalidClassException: SampleBean; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:715)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1997)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1866)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2159)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1685)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:499)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:457)
	at JspTest.doGet(JspTest.java:27)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:832)

J'obtiens l'erreur ʻInvalidClassException` et je sais que la cause est une discordance dans le serialVersionUID de SampleBean. Au contraire, que se passe-t-il si serialVersionUID a la même valeur mais que le contenu est différent? Ne modifiez pas le SampleBean de la personne qui sérialise, et seule la personne qui désérialise essaiera x100 dans le getter comme indiqué ci-dessous (dans getPropertyE ()).

SampleBean.java (modification uniquement du côté désérialisation)


import java.io.Serializable;

public class SampleBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private double propertyD;
    private int propertyE;

    public double getPropertyD() {
        return propertyD;
    }

    public void setPropertyD(double propertyD) {
        this.propertyD = propertyD;
    }

    public int getPropertyE() {
        return propertyE * 100;    //Essayez x100 dans getter
    }

    public void setPropertyE(int propertyE) {
        this.propertyE = propertyE;
    }
}

Résultat de l'exécution (affichage du navigateur) image.png

Vous pouvez voir qu'aucune erreur ne s'est produite et la valeur x100 est affichée. Bien qu'il se soit terminé normalement, Ce comportement est-il vraiment celui que vous attendiez? </ font> Avez-vous déjà envisagé le comportement lorsque le contenu du programme est modifié entre le moment de Serialize et le moment de Deserialize? Ce que Deserialize restaure est la valeur de la variable d'instance. Au moment de la sérialisation, le type double 100 est défini pour propertyD et le type int 100 est défini pour propertyE, de sorte que Deserialize restaure cette valeur. Je me fiche de ce que je fais dans le setter / getter.

image.png

Ainsi, par exemple, même si vous vérifiez dans le setter qu'une erreur se produira si la valeur de l'argument est 10 ou plus, la valeur qui l'ignore sera restaurée.

SampleBean.java


import java.io.Serializable;

public class SampleBean implements Serializable {
    private static final long serialVersionUID = 1L;

    private double propertyD;
    private int propertyE;

    public double getPropertyD() {
        return propertyD;
    }

    public void setPropertyD(double propertyD) {
        //Erreur si la valeur de l'argument est égale ou supérieure à 10
        if (propertyD > 10) {
            throw new IllegalArgumentException();
        }
        this.propertyD = propertyD;
    }

    public int getPropertyE() {
        return propertyE;
    }

    public void setPropertyE(int propertyE) {
        this.propertyE = propertyE;
    }
}

Résultat de l'exécution (affichage du navigateur) image.png

S'il est de 10 ou plus dans le setter, il peut être affiché sans aucune erreur même s'il est vérifié comme erreur. Plus tôt dans cet article, j'ai mentionné que les informations de session sont sérialisées / désérialisées. Le serveur Web sérialise les informations de session dans les cas suivants.

--Lorsque le serveur est arrêté --Lorsque la zone (mémoire) de stockage de la session est pleine

Désérialiser au moment opposé. Même si le programme est modifié entre le moment où il est sérialisé et le moment où il est désérialisé, le changement sera ignoré et le programme sera désérialisé. Si cela vous dérange, vous devez changer le serialVersionUID.

Les spécifications Java pour Serialize / Deserialize sont résumées ci-dessous. 5.6 Modifications de type affectant la sérialisation Ce dont je voudrais que vous soyez particulièrement conscient est "5.6.2 Modifications compatibles", et même si les spécifications Java sont compatibles (= Désérialisation possible), elles sont compatibles avec le système que vous utilisez réellement. Qu'il y ait ou non une autre histoire. Est-il vraiment acceptable de désérialiser si elle est compatible en tant que spécification Java? Si quelque chose d'étrange se produit si vous n'utilisez pas à nouveau l'écran, il peut être plus sûr de changer le serialVersionUID et de générer une erreur.

Comment changer serialVersionUID

Vous pouvez le modifier vous-même, mais vous pouvez également laisser l'EDI le générer. Pour Eclipse, la procédure est la suivante. (Si vous utilisez IntelliJ IDEA, ici) Supprimez private static final long serialVersionUID = 1L; de la classe. Ensuite, un avertissement apparaîtra dans la classe, alors placez le curseur dessus (voir la figure ci-dessous).

image.png

Un serialVersionUID de private static final long serialVersionUID = 2779728212248637579L; a été généré. Cette valeur changera à mesure que la structure de la classe change. La structure d'une classe consiste à ajouter une variable d'instance, à renommer une variable, à ajouter une méthode, etc.

Exclure la sérialisation / la désérialisation avec transitoire

En ajoutant le modificateur transitoire à la variable d'instance, il peut être exclu de Serialize / Deserialize. À titre de test, ajoutez le modificateur transitoire à propertyE comme indiqué ci-dessous.

SampleBean.java


import java.io.Serializable;

public class SampleBean implements Serializable {

    private static final long serialVersionUID = 4798282072280430232L;
    private double propertyD;
    private transient int propertyE;  //Accorder transitoire

    public double getPropertyD() {
        return propertyD;
    }

    public void setPropertyD(double propertyD) {
        this.propertyD = propertyD;
    }

    public int getPropertyE() {
        return propertyE;
    }

    public void setPropertyE(int propertyE) {
        this.propertyE = propertyE;
    }
}

Défini sur 100 lors de la sérialisation.

WriteObject.java


import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

public class WriteObject {
    public static void main (String args[]) {
        SampleBean sample = new SampleBean();
        sample.setPropertyD(100);
        sample.setPropertyE(100);

        try {
            FileOutputStream fout = new FileOutputStream("sample.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fout);
            oos.writeObject(sample);
            oos.close();
            System.out.println("Done");
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}

Le résultat du chargement du fichier sample.ser généré ici et de son affichage sur le navigateur est le suivant.

Résultat de l'exécution (affichage du navigateur) image.png

En ajoutant transitoire à propertyE, vous pouvez voir que la valeur de 100 définie au moment de la sérialisation n'est pas définie au moment de la désérialisation. 0 est affiché car la valeur par défaut de int est 0. À propos, si vous ajoutez transitoire, la valeur de serialVersionUID générée par l'EDI changera également.

en conclusion

C'est à peu près comme ça. Je ne comprends pas grand-chose, mais j'ai senti qu'il n'y avait pas beaucoup de gens autour de moi qui ont écrit ʻimplements Serializable ou serialVersionUID = 1L après avoir bien compris ce domaine, alors je l'ai écrit. Je l'ai essayé. J'espère que le plus grand nombre de personnes possible après avoir lu cet article comprendra la signification de Serialize et écrira ʻimplements Serializable ou serialVersionUID = 1L.

c'est tout.

référence

officiel

Sérialiser: sérialiser et désérialiser: désérialiser

Sérialisation et session

serialVersionUID

Gestion de session Tomcat

Recommended Posts

Comprendre le sérialisable de Java tout en exécutant l'application
[java8] Pour comprendre l'API Stream
Référence Java à comprendre dans la figure
Sérialisable de java
[Java] Comprendre la différence entre List et Set
Vérifiez les options définies pour le processus Java en cours d'exécution
[Java / PostgreSQL] Connectez l'application WEB à la base de données
État de l'application Java 9+
Comprendre le constructeur java
[Tutoriel] Télécharger Eclipse → Lancer l'application avec Java (Pléiades)
Exécutez Maven sur Java 8 lors de la compilation sur Java 6 et des tests sur Java 11
[Pour les débutants] Comprendre rapidement les bases de Java 8 lambda
Classes et instances Java comprises dans la figure