When you create a web application in Java, you may create a JavaBeans class like the one below.
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;
}
}
Do you understand what these ʻimplements Serializable and
serialVersionUID = 1L; `mean?
This article was written for people who say "I don't know!" People who say "I understand!" Do not need to read this article.
Outputting a Java instance as a byte array is called Serialize, and vice versa is called Deserialize. As you can see in the figure below, saving an instance in a file, memory, database, etc. is called Serialize.
The figure is excerpted from this article
So declaring ʻimplements Serializable` means declaring" This instance can be saved to disk etc.! ".
I have a web application that I made a little personally, so I will experiment with it.
TestServlet.java
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
//・ ・ ・
}
The above TestServlet
extendsHttpServlet
, HttpServlet
extendsGenericServlet
, and GenericServlet
implementsSerializable
, so TestServlet
isSerializable
.
Start the server (Tomcat) for this web application. There is nothing under the localhost folder of the deployment destination yet.
After this, open a browser or do something to make TestServlet
work and stop the server. Then, a mysterious file called SESSIONS.ser
will be created directly under the localhost folder of the deployment destination.
Start the server again. Then SESSIONS.ser
disappears.
SESSIONS.ser
is the serialized session information by Tomcat. Thanks to this, the session information is not lost even if the server is restarted, and the information before the restart can be restored (Deserialize) by accessing it after the restart. Conversely, for this specification, the objects stored in the session must implement Serializable.
It seems convenient at first glance, but do you know what happens if the program is changed before and after the reboot?
Create an application with the configuration shown below and actually serialize / Deserialize it.
First of all, the person who serializes. Create SampleBean.java
and WriteObject.java
to serialize it in any folder. WriteObject.java creates a SampleBean instance, sets two properties to 100 and serializes it. Create sample.ser
by executing WriteObject. sample.ser is a Serialized version of the SampleBean instance.
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();
}
}
}
Next is the person who deserializes.
Create a class (JspTest.java) that reads the sample.ser
generated above.
The SampleBean class used in JspTest.java uses the same contents as the previous one by copying it to another folder.
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 to display it
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>Property D</th>
<th>Property 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>
Execution result (browser display)
I was able to deserialize the serialized instance and display it in the browser. This is Serialize / Deserialize.
Next is serialVersionUID
. If you write implements Serializable in your class and don't declare serialVersionUID, you'll get a warning that The serializable class XXXX does not declare a static final serialVersionUID field of type long
.
serialVersionUID
is the version of the instance. I don't think it's very clear, so I'll do a little experiment to understand it more concretely.
Let's make the SerialVersionUID of SampleBean.java used when Serializing earlier and the serialVersionUID of SampleBean.java used when deserializing it inconsistent.
Change SampleBean.java (the one that Deserializes) used by JspTest.java to serialVersionUID = 2L;
. The serialized SampleBean.java remains serialVersionUID = 1L;
.
After making the change, if you update the browser with F5, the following error will be output.
error contents
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)
I get the error ʻInvalidClassException` and know that the cause is a mismatch in the serialVersionUID of the SampleBean. On the contrary, what happens if the serialVersionUID has the same value but the contents are different? Do not change the SampleBean of the serialize person, and only the Deserialize person will try x100 in the getter as shown below (in getPropertyE ()).
SampleBean.java (change only on Deserialize side)
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; //Try x100 in the getter
}
public void setPropertyE(int propertyE) {
this.propertyE = propertyE;
}
}
Execution result (browser display)
You can see that no error has occurred and the value x100 is displayed. Although it ended normally, Is this behavior really what you expected? </ font> Have you ever considered the behavior when the contents of the program are changed between Serialize and Deserialize? What Deserialize restores is the value of the instance variable. At the time of serialization, double type 100 is set for propertyD and int type 100 is set for propertyE, so Deserialize restores that value. I don't care what I'm doing in the setter / getter.
So, for example, even if you check in the setter that an error will occur if the argument value is 10 or more, the value that ignores it will be restored.
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) {
//Error if the argument value is 10 or more
if (propertyD > 10) {
throw new IllegalArgumentException();
}
this.propertyD = propertyD;
}
public int getPropertyE() {
return propertyE;
}
public void setPropertyE(int propertyE) {
this.propertyE = propertyE;
}
}
Execution result (browser display)
If it is 10 or more in setter, it can be displayed without any error even though it is checked as error. Earlier in this article, I mentioned that session information is Serialized / Deserialized. The web server serializes session information in the following cases.
--When the server is stopped --When the area (memory) for storing the session is full
Deserialize at the opposite timing. Even if the program is changed between the time it is serialized and the time it is deserialized, the change will be ignored and the program will be deserialized. If that bothers you, you need to change the serialVersionUID.
The Java specifications for Serialize / Deserialize are summarized below. 5.6 Type changes affecting serialization What I would like you to be especially aware of is "5.6.2 compatible changes", and even if the Java specifications are compatible (= Deserialize possible), they are compatible with the system you are actually using. Whether or not there is another story. Is it really okay to deserialize if it is compatible as a Java specification? If something strange happens if you do not operate the screen again, it may be safer to change the serialVersionUID and generate an error.
You can change it by yourself, but you can also let the IDE generate it. In Eclipse, the procedure is as follows.
(If you are using IntelliJ IDEA, here)
Remove private static final long serialVersionUID = 1L;
from the class. Then, a warning will appear in the class, so place the cursor there (see the figure below).
A serialVersionUID of private static final long serialVersionUID = 2779728212248637579L;
was generated. This value will change as the structure of the class changes. The structure of a class is adding an instance variable, renaming a variable, adding a method, and so on.
By adding the transient modifier to the instance variable, it can be excluded from Serialize / Deserialize. As a test, add the transient modifier to propertyE as shown below.
SampleBean.java
import java.io.Serializable;
public class SampleBean implements Serializable {
private static final long serialVersionUID = 4798282072280430232L;
private double propertyD;
private transient int propertyE; //Grant transient
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;
}
}
Set to 100 when serializing.
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();
}
}
}
The result of loading the sample.ser generated here and displaying it in the browser is as follows.
Execution result (browser display)
By adding transient to propertyE, you can see that the value 100 set during Serialize is not set when Deserialize. 0 is displayed because the default value of int is 0. By the way, adding transient also changes the value of serialVersionUID generated by the IDE.
It's roughly like this. There are some parts that I don't understand well, but I felt that there weren't many people around me who wrote ʻimplements Serializable or
serialVersionUID = 1L after understanding this area properly, so I wrote it. I tried it. I hope that as many people as possible after reading this article will understand the meaning of Serialize and write ʻimplements Serializable
or serialVersionUID = 1L
.
that's all.
official
-5.6 Type changes affecting serialization -System Architecture --Version control of serializable objects
Serialize: Serialize and Deserialize: Deserialize
Serialize and session
-[3.1.14 How to create a Web application when using the session recovery function](https://software.fujitsu.com/jp/manual/manualfiles/m140003/b1ws1025/04z200/b1025-00-03-01-14 .html) -Object saved in session is not serializable -Object registered in session must be Serializable -[[JAVA] NullPointerException and Cannot get property ‘xxxxxx’ on null object] at failover or restart (https://huge.mints.ne.jp/11/2013/it_technique/groovy/grails/512/)
serialVersionUID
-Understanding serialVersionUID -How to automatically generate the serial version UID of Eclipse Java -Generate serialVersionUID in IntelliJ IDEA
Tomcat session management
-Tomcat session persistence -(Working) tomcat restart part 44 session manager
Recommended Posts