Verstehen Sie Java Serializable, während Sie die Anwendung tatsächlich ausführen

Verstehst du die Bedeutung von Serializable?

Wenn Sie eine Webanwendung in Java erstellen, können Sie eine JavaBeans-Klasse wie die folgende erstellen.

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;
    }
}

Verstehst du die Bedeutung dieser "implementiert Serializable" und "serialVersionUID = 1L;"? Dieser Artikel wurde für Leute geschrieben, die sagen "Ich weiß nicht!" Leute, die sagen "Ich verstehe!", Müssen diesen Artikel nicht lesen.

Zunächst aus dem Konzept

Die Ausgabe einer Java-Instanz als Byte-Array wird als Serialize und umgekehrt als Deserialize bezeichnet. Wie Sie in der folgenden Abbildung sehen können, wird das Speichern einer Instanz in einer Datei, einem Speicher, einer Datenbank usw. als Serialize bezeichnet.

image.png Die Abbildung ist ein Auszug aus diesem Artikel.

Das Deklarieren von "implementiert Serializable" bedeutet also das Deklarieren von "Diese Instanz kann auf der Festplatte usw. gespeichert werden!".

Ich habe eine Webanwendung, die ich ein wenig persönlich erstellt habe, also werde ich damit experimentieren.

TestServlet.java


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

Das obige TestServlet erweitertHttpServlet, HttpServlet erweitertGenericServlet und GenericServlet implementiertSerializable, sodass TestServlet``Serializable ist.

image.png

Starten Sie den Server (Tomcat) für diese Webanwendung. Im Ordner localhost des Bereitstellungsziels befindet sich noch nichts.

image.png

Öffnen Sie danach den Browser oder tun Sie etwas, damit TestServlet funktioniert, und stoppen Sie den Server. Anschließend wird eine mysteriöse Datei mit dem Namen "SESSIONS.ser" direkt unter dem Ordner "localhost" des Bereitstellungsziels erstellt.

image.png

Starten Sie den Server erneut. Dann verschwindet SESSIONS.ser.

image.png

SESSIONS.ser ist eine serialisierte Sitzungsinformation von Tomcat. Dank dessen gehen die Sitzungsinformationen auch beim Neustart des Servers nicht verloren, und die Informationen vor dem Neustart können wiederhergestellt (deserialisiert) werden, indem nach dem Neustart darauf zugegriffen wird. Umgekehrt müssen für diese Spezifikation die in der Sitzung gespeicherten Objekte Serializable implementieren. Auf den ersten Blick scheint es praktisch, aber wissen Sie, was passiert, wenn das Programm vor und nach dem Neustart geändert wird?

Lassen Sie uns tatsächlich Serialize / Deserialize machen

Erstellen Sie eine Anwendung mit der unten gezeigten Konfiguration und lassen Sie sie tatsächlich serialisieren / deserialisieren.

image.png

Zuallererst derjenige, der serialisiert. Erstellen Sie "SampleBean.java" und "WriteObject.java", um es in einem beliebigen Ordner zu serialisieren. WriteObject.java erstellt eine SampleBean-Instanz, setzt zwei Eigenschaften auf 100 und serialisiert sie. Erstellen Sie "sample.ser", indem Sie WriteObject ausführen. sample.ser ist eine serialisierte Version der SampleBean-Instanz.

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();
        }
    }
}

Als nächstes kommt die Person, die deserialisiert. Erstellen Sie eine Klasse (JspTest.java), die den oben generierten sample.ser liest. Die in JspTest.java verwendete SampleBean-Klasse ist dieselbe wie die vorherige, die in einen anderen Ordner kopiert wurde.

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, um es anzuzeigen

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>Eigenschaft D.</th>
      <th>Eigenschaft 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>

Ausführungsergebnis (Browseranzeige) image.png

Ich konnte die serialisierte Instanz deserialisieren und im Browser anzeigen. Dies ist Serialize / Deserialize.

Was ist serialVersionUID?

Als nächstes kommt serialVersionUID. Wenn Sie Implementierungen Serializable in Ihre Klasse schreiben und keine serialVersionUID deklarieren, wird die Warnung "Die serialisierbare Klasse XXXX deklariert kein statisches endgültiges serialVersionUID-Feld vom Typ long" angezeigt. serialVersionUID ist die Version der Instanz. Ich denke nicht, dass es sehr klar ist, also werde ich ein kleines Experiment machen, um es konkreter zu verstehen. Lassen Sie uns die SampleBean.java, die beim früheren Serialisieren verwendet wird, und die serialVersionUID von SampleBean.java, die beim Deserialisieren verwendet wird, nicht übereinstimmen. Ändern Sie SampleBean.java (dasjenige, das deserialisiert), das von JspTest.java verwendet wird, in "serialVersionUID = 2L;". Die serialisierte SampleBean.java bleibt "serialVersionUID = 1L;". Wenn Sie nach der Änderung den Browser mit F5 aktualisieren, wird der folgende Fehler ausgegeben.

Fehlerinhalt


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)

Ich erhalte den Fehler "InvalidClassException" und weiß, dass die Ursache eine Nichtübereinstimmung in der serialVersionUID der SampleBean ist. Was passiert im Gegenteil, wenn die serialVersionUID den gleichen Wert hat, der Inhalt jedoch unterschiedlich ist? Ändern Sie nicht die SampleBean der Person, die serialisiert, und nur die Person, die deserialisiert, versucht x100 im Getter wie unten gezeigt (in getPropertyE ()).

SampleBean.Java (nur auf der Deserialize-Seite ändern)


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;    //Versuchen Sie x100 in Getter
    }

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

Ausführungsergebnis (Browseranzeige) image.png

Sie können sehen, dass kein Fehler aufgetreten ist und der Wert x100 angezeigt wird. Obwohl es normal endete, Ist dieses Verhalten wirklich das, was Sie erwartet haben? </ font> Haben Sie jemals über das Verhalten nachgedacht, wenn der Inhalt des Programms zwischen dem Zeitpunkt der Serialisierung und dem Zeitpunkt der Deserialisierung geändert wird? Was Deserialize wiederherstellt, ist der Wert der Instanzvariablen. Zum Zeitpunkt der Serialisierung wird für propertyD der Doppeltyp 100 und für propertyE der Int-Typ 100 festgelegt, sodass Deserialize diesen Wert wiederherstellt. Es ist mir egal, was ich in Setter / Getter mache.

image.png

Selbst wenn Sie beispielsweise im Setter einchecken, dass ein Fehler auftritt, wenn der Argumentwert 10 oder mehr beträgt, wird der Wert, der ihn ignoriert, wiederhergestellt.

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) {
        //Fehler, wenn der Argumentwert 10 oder mehr beträgt
        if (propertyD > 10) {
            throw new IllegalArgumentException();
        }
        this.propertyD = propertyD;
    }

    public int getPropertyE() {
        return propertyE;
    }

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

Ausführungsergebnis (Browseranzeige) image.png

Wenn der Setter 10 oder mehr beträgt, kann er fehlerfrei angezeigt werden, obwohl er als Fehler markiert ist. Zu Beginn dieses Artikels habe ich erwähnt, dass Sitzungsinformationen serialisiert / deserialisiert sind. Der Webserver serialisiert die Sitzungsinformationen in den folgenden Fällen.

--Wenn der Server gestoppt ist --Wenn der Bereich (Speicher) zum Speichern der Sitzung voll ist

Zum entgegengesetzten Zeitpunkt deserialisieren. Selbst wenn das Programm zwischen dem Zeitpunkt der Serialisierung und dem Zeitpunkt der Deserialisierung geändert wird, wird die Änderung ignoriert und das Programm wird deserialisiert. Wenn Sie das stört, müssen Sie die serialVersionUID ändern.

Die Java-Spezifikationen für Serialize / Deserialize sind unten zusammengefasst. 5.6 Typänderungen, die sich auf die Serialisierung auswirken Ich möchte, dass Sie besonders auf "5.6.2 Kompatible Änderungen" aufmerksam werden. Auch wenn die Java-Spezifikationen kompatibel sind (= Deserialisieren möglich), sind sie mit dem System kompatibel, das Sie tatsächlich verwenden. Ob es eine andere Geschichte gibt oder nicht. Ist es wirklich in Ordnung zu deserialisieren, wenn es als Java-Spezifikation kompatibel ist? Wenn etwas Seltsames passiert, wenn Sie den Bildschirm nicht erneut bedienen, ist es möglicherweise sicherer, die serialVersionUID zu ändern und einen Fehler zu generieren.

So ändern Sie die serialVersionUID

Sie können es selbst ändern, aber Sie können es auch von der IDE generieren lassen. Für Eclipse ist die Vorgehensweise wie folgt. (Wenn Sie IntelliJ IDEA verwenden, hier) Entfernen Sie "private static final long serialVersionUID = 1L;" aus der Klasse. Dann erscheint eine Warnung in der Klasse. Setzen Sie den Cursor darauf (siehe Abbildung unten).

image.png

Eine serialVersionUID von "private static final long serialVersionUID = 2779728212248637579L;" wurde generiert. Dieser Wert ändert sich, wenn sich die Struktur der Klasse ändert. Die Struktur einer Klasse besteht darin, eine Instanzvariable hinzuzufügen, eine Variable umzubenennen, eine Methode hinzuzufügen usw.

Serialize / Deserialize mit transient ausschließen

Durch Hinzufügen des Transientenmodifikators zur Instanzvariablen kann er von Serialize / Deserialize ausgeschlossen werden. Fügen Sie als Test den Transientenmodifikator wie unten gezeigt zu propertyE hinzu.

SampleBean.java


import java.io.Serializable;

public class SampleBean implements Serializable {

    private static final long serialVersionUID = 4798282072280430232L;
    private double propertyD;
    private transient int propertyE;  //Transient gewähren

    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;
    }
}

Beim Serialisieren auf 100 setzen.

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();
        }
    }
}

Das Ergebnis des Ladens der hier generierten sample.ser und ihrer Anzeige im Browser ist wie folgt.

Ausführungsergebnis (Browseranzeige) image.png

Durch Hinzufügen von Transient zu propertyE können Sie sehen, dass der zum Zeitpunkt der Serialisierung festgelegte Wert 100 zum Zeitpunkt der Deserialisierung nicht festgelegt ist. 0 wird angezeigt, da der Standardwert von int 0 ist. Wenn Sie transient hinzufügen, ändert sich übrigens auch der Wert der von der IDE generierten serialVersionUID.

abschließend

Es ist ungefähr so. Es gibt einen Teil, den ich nicht gut verstehe, aber ich hatte das Gefühl, dass es nicht viele Leute um mich gibt, die "implementiert Serializable" oder "serialVersionUID = 1L" schreiben, nachdem sie diesen Bereich richtig verstanden haben, also habe ich ihn geschrieben. Ich versuchte es. Ich hoffe, dass so viele Menschen wie möglich nach dem Lesen dieses Artikels die Bedeutung von Serialize verstehen und "implementiert Serializable" oder "serialVersionUID = 1L" schreiben.

das ist alles.

Referenz

offiziell

Serialisieren: Serialisieren und Deserialisieren: Deserialisieren

Serialisieren und Sitzung

serialVersionUID

Tomcat-Sitzungsverwaltung

Recommended Posts

Verstehen Sie Java Serializable, während Sie die Anwendung tatsächlich ausführen
[java8] Um die Stream-API zu verstehen
Java-Referenz zum Verständnis in der Abbildung
Serialisierbar von Java
[Java] Verstehe den Unterschied zwischen List und Set
Überprüfen Sie die für den laufenden Java-Prozess festgelegten Optionen
[Java / PostgreSQL] Verbinden Sie die WEB-Anwendung mit der Datenbank
Java 9+ Anwendungsstatus
Verstehen Sie den Java-Konstruktor
[Tutorial] Eclipse herunterladen → Anwendung mit Java ausführen (Plejaden)
Führen Sie Maven unter Java 8 aus, während Sie unter Java 6 kompilieren und unter Java 11 testen
[Für Anfänger] Verstehen Sie schnell die Grundlagen von Java 8 Lambda
In der Abbildung verstandene Java-Klassen und -Instanzen