catalogue
3. Serialization and deserialization API in JDK
(1) Class does not implement the Serializable interface, which is an example of serialization
(2) Class implements the Serializable interface for serialization and deserialization
5. Function of transient keyword
(1) The transient property is not serialized
(2) The static property is not serialized
6. Function of Externalizable interface
7. Issues needing attention in serialization
8. Deserialization vulnerability
1. Basic concepts
-
Serialization: refers to the process of converting java objects into byte sequences;
-
Deserialization: refers to the process of restoring byte sequences to java objects;
-
The serialized entity is an object, and the result is also an object, not formatted text. The text record we see in Notepad is not the result of object serialization, but the formatted text output by the object. The real serialized object cannot be understood.
-
The result of serialization is a file only known to the JAVA virtual machine. People do not participate, but are only used to save objects or transfer.
2. Usage scenario
When you want to call another jvm object from a jvm, you can consider serialization. Serialization is a solution for sharing instance objects between different JVMs.
Object serialization has two main purposes:
(1) Save the byte sequence of objects to the hard disk, usually in a file: in many applications, some objects need to be serialized to leave the memory space and live in the physical hard disk for long-term storage. For example, the most common is the Session object in the Web server. When 100000 users access concurrently, 100000 Session objects may appear, and the memory may not be enough. Therefore, the Web container will serialize some sessions to the hard disk first, and then restore the objects saved in the hard disk to the memory when they need to be used;
(2) Transfer the byte sequence of objects on the network: when two processes are communicating remotely, they can send various types of data to each other. No matter what type of data, it will be transmitted on the network in the form of binary sequence. The sender needs to convert the Java object into a byte sequence before it can be transmitted on the network; The receiver needs to restore the byte sequence to a Java object;
3. Serialization and deserialization API in JDK
3.1 basic principles
-
java.io.ObjectOutputStream represents the object output stream. Its writeObject(Object obj) method can serialize the obj object specified by the parameter and write the resulting byte sequence to a target output stream;
-
java.io.ObjectInputStream represents the object input stream. Its readObject() method reads byte sequences from a source input stream, deserializes them into an object, and returns them;
-
Only objects of classes that implement the Serializable interface or Externalizable interface can be serialized;
-
If only a class implements the Serializable interface without any other processing, the default serialization mechanism is used. Using the default mechanism, when serializing an object, it will not only serialize the current object itself, but also serialize other objects referenced by the object. Similarly, other objects referenced by these other objects will also be serialized, and so on. Therefore, if the member variables contained in an object are container class objects, and the elements contained in these containers are also container class objects, the serialization process will be more complex and expensive.
-
Object serialization includes the following steps:
(1) Create an object output stream, which can package other types of target output streams, such as file output stream and byte array output stream;
(2) Write the object through the writeObject() method of the object output stream;
-
The steps of object deserialization are as follows:
(1) Create an object input stream, which can wrap other types of source input streams, such as file input stream and byte array input stream;
(2) Read the object through the readObject() method of the object input stream;
3.2 example
(1) Class does not implement the Serializable interface, which is an example of serialization
Define a class that does not implement Serializable: User
public class User { private Integer userId; private String userName; public Integer getUserId() { return userId; } @Override public String toString() { return "User [userId=" + userId + ", userName=" + userName + "]"; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
Serialization test:
public class Test { public static void main(String[] args) throws Exception { serialize(); User o = (User) deSerialize(); System.out.println(o.toString()); } public static void serialize() throws IOException { User u = new User(); u.setUserId(1234567); u.setUserName("zhangsan"); FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectOutputStream os = new ObjectOutputStream(fo); os.writeObject(u); } public static Object deSerialize() throws IOException, ClassNotFoundException { FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectInputStream oi = new ObjectInputStream(fi); return oi.readObject(); } }
The operation results are as follows:
(2) Class implements the Serializable interface for serialization and deserialization
Make the above User class implement the Serializable interface:
public class User implements Serializable { private Integer userId; private String userName; public Integer getUserId() { return userId; } @Override public String toString() { return "User [userId=" + userId + ", userName=" + userName + "]"; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
Run the above serialization test, and the results are as follows (the operation is successful, and an a.txt file is also generated under the corresponding local path):
User [userId=1234567, userName=zhangsan]
4. Role of serialVersionUID
4.1 function
serialVersionUID function: in order to maintain version compatibility during serialization, that is, during version upgrade, deserialization still maintains the uniqueness of objects. There are two generation methods:
(1) One is the default 1L, for example: private static final long serialVersionUID = 1L;
(2) One is to generate a 64 bit hash field according to class name, interface name, member method and attribute, such as: private static final # long # serialVersionUID = XXXL;
4.2 example
Add serialVersionUID to the above User class:
public class User implements Serializable { private static final long serialVersionUID = 111L; private Integer userId; private String userName; public Integer getUserId() { return userId; } @Override public String toString() { return "User [userId=" + userId + ", userName=" + userName + "]"; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
Execute the above test and run as follows (the operation is successful, and an a.txt file is also generated under the corresponding local path):
User [userId=1234567, userName=zhangsan]
Change the serialVersionUID in the above User class:
public class User implements Serializable { private static final long serialVersionUID = 222L; private Integer userId; private String userName; public Integer getUserId() { return userId; } @Override public String toString() { return "User [userId=" + userId + ", userName=" + userName + "]"; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
public class Test { public static void main(String[] args) throws Exception { //serialize(); User o = (User) deSerialize(); System.out.println(o.toString()); } public static void serialize() throws IOException { User u = new User(); u.setUserId(1234567); u.setUserName("zhangsan"); FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectOutputStream os = new ObjectOutputStream(fo); os.writeObject(u); } public static Object deSerialize() throws IOException, ClassNotFoundException { FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectInputStream oi = new ObjectInputStream(fi); return oi.readObject(); } }
The operation results are as follows:
Class and the class in the classpath, that is, the modified class, are incompatible. Considering the security mechanism, the program throws an error and refuses to load. So what if we really need to add a field or method after serialization? What should I do? That is to specify the serialVersionUID yourself. Therefore, it is strongly recommended to define the serialVersionUID displayed in a serializable class and give it an explicit value.
5. Function of transient keyword
5.1 function
-
In the actual development process, we often encounter such a problem: some attributes of the class need to be serialized, while other attributes do not need to be serialized. For example, the user has some sensitive information (such as mima, bank card number, etc.). For security reasons, he does not want to be transmitted in network operations (mainly involving serialization operations, and local serialization cache is also applicable). The variables corresponding to these information can be added with the transient key word. In other words, the life cycle of this field is only stored in the caller's memory and will not be written to disk for persistence;
-
The transient keyword of java provides us with convenience. We only need to implement the serializable interface and add the keyword transient before the attribute that does not need to be serialized. When serializing an object, this attribute will not be serialized to the specified destination;
5.2 example
(1) The transient property is not serialized
Add a new home address attribute for the User class, and add the transient keyword:
public class User implements Serializable { private static final long serialVersionUID = 222L; private Integer userId; private String userName; private transient String address; public Integer getUserId() { return userId; } @Override public String toString() { return "User [userId=" + userId + ", userName=" + userName + ", address =" + address + "]"; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
Set home address and perform serialization & deserialization test:
public class Test { public static void main(String[] args) throws Exception { serialize(); User o = (User) deSerialize(); System.out.println(o.toString()); } public static void serialize() throws IOException { User u = new User(); u.setUserId(1234567); u.setUserName("zhangsan"); u.setAddress("999 Nanhu South Road"); FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectOutputStream os = new ObjectOutputStream(fo); os.writeObject(u); } public static Object deSerialize() throws IOException, ClassNotFoundException { FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectInputStream oi = new ObjectInputStream(fi); return oi.readObject(); } }
The result is as follows (the address property is not serialized):
User [userId=1234567, userName=zhangsan, address =null]
(2) The static property is not serialized
Add a new group attribute for the User class and add the static keyword:
public class User implements Serializable { private static final long serialVersionUID = 222L; private Integer userId; private String userName; private transient String address; private static Integer groupId; public static Integer getGroupId() { return groupId; } public static void setGroupId(Integer groupId) { User.groupId = groupId; } public Integer getUserId() { return userId; } @Override public String toString() { return "User [userId=" + userId + ", userName=" + userName + ", address =" + address + ", groupId =" + groupId + "]"; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
Perform deserialization test (serialize() has been performed before deserialization):
public class Test { public static void main(String[] args) throws Exception { //serialize(); User o = (User) deSerialize(); System.out.println(o.toString()); } public static void serialize() throws IOException { User u = new User(); u.setUserId(1234567); u.setUserName("zhangsan"); u.setAddress("999 Nanhu South Road"); User.setGroupId(8); FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectOutputStream os = new ObjectOutputStream(fo); os.writeObject(u); } public static Object deSerialize() throws IOException, ClassNotFoundException { FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectInputStream oi = new ObjectInputStream(fi); return oi.readObject(); } }
The result is as follows (the group attribute is not serialized):
User [userId=1234567, userName=zhangsan, address =null, groupId =null]
If serialization & deserialization tests are performed at the same time:
public class Test { public static void main(String[] args) throws Exception { serialize(); User o = (User) deSerialize(); System.out.println(o.toString()); } public static void serialize() throws IOException { User u = new User(); u.setUserId(1234567); u.setUserName("zhangsan"); u.setAddress("999 Nanhu South Road"); User.setGroupId(8); FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectOutputStream os = new ObjectOutputStream(fo); os.writeObject(u); } public static Object deSerialize() throws IOException, ClassNotFoundException { FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectInputStream oi = new ObjectInputStream(fi); return oi.readObject(); } }
The results are as follows (group attributes are also serialized):
User [userId=1234567, userName=zhangsan, address =null, groupId =8]
It was found that the group attribute had a value. At this time, because this value is not read from the file by deserialization, it is the value of the corresponding static variable in the current JVM.
6. Function of Externalizable interface
6.1 function
Externalizable is a subclass of the Serializable interface. If you do not want to serialize so many attributes, you can use this interface. The writeExternal() and readExternal() methods of this interface can specify which attributes to serialize;
6.2 example
Define the User class, implement the Externalizable interface, and override the writeExternal and readExternal methods:
public class User implements Externalizable { private static final long serialVersionUID = 222L; private Integer userId; private String userName; private Integer sex; public Integer getUserId() { return userId; } @Override public String toString() { return "User [userId=" + userId + ", userName=" + userName + ", sex =" + sex + "]"; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Integer getSex() { return sex; } public void setSex(Integer sex) { this.sex = sex; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(userId); out.writeObject(sex); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { userId = (Integer) in.readObject(); sex = (Integer) in.readObject(); } }
Serialization test:
public class Test { public static void main(String[] args) throws Exception { serialize(); User o = (User) deSerialize(); System.out.println(o.toString()); } public static void serialize() throws IOException { User u = new User(); u.setUserId(1234567); u.setUserName("zhangsan"); u.setSex(1); FileOutputStream fo = new FileOutputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectOutputStream os = new ObjectOutputStream(fo); os.writeObject(u); } public static Object deSerialize() throws IOException, ClassNotFoundException { FileInputStream fi = new FileInputStream(new File("/Users/lydia/Downloads/a.txt")); ObjectInputStream oi = new ObjectInputStream(fi); return oi.readObject(); } }
The test results are as follows:
User [userId=1234567, userName=null, sex =1]
7. Issues needing attention in serialization
1. When serializing, only the state of the object is saved, regardless of the method of the object;
2. When a parent class implements serialization, the child class automatically implements serialization without explicitly implementing the Serializable interface;
3. When the instance variable of an object references other objects, the reference object is also serialized when the object is serialized;
4. If an object has private, public and other fields, the private and other fields of the object are not protected during serialization and transmission. Therefore, for security reasons, not all objects can be serialized;
8. Deserialization vulnerability
Deserialization vulnerability is not unique to java. Other languages will also have it, but it is the same that the injected code is executed during deserialization.
The detailed principle is stamped here 👉 Principle analysis of Java deserialization vulnerability