Java serialization
What is serialization?
Object and binary conversion.
What is the purpose of conversion?
Objects are converted to binary, and then the binary is restored to objects.
This applies to writing objects to disk files or, more commonly, to remote machines (such as the Dubbo RPC framework).
What is Java serialization?
Java serialization is a bit special, converting objects to byte arrays (byte[]). But byte arrays are also binary by nature.
What does serialization do?
So serialization in other languages, as well as in the Java language, is essentially about recovering objects from disk files or remote machines.
Official Documentation
Serialization is used for lightweight persistence and for communication via sockets or Java Remote Method Invocation (Java RMI).
Docs.oracle.com/javase/7/do…
How is serialization implemented?
Implement serialization interface
public class Person implements Serializable {
private static final long serialVersionUID = 2709425275741743919L;
}
Copy the code
demo
The POJO class is primarily intended to implement the serialization interface.
package test2;
import java.io.Serializable;
public class Person implements Serializable { // Implement the serialization interface
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
private String address;
public Person(a) {}public Person(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
public String toString(a) {
return "test2.Person{" +
"name='" + name + ' '' + ", age=" + age + ", address='" + address + ''' + '}'; }}Copy the code
Test class, core steps
- serialization
The object is converted to binary (that is, byte arrays) and then written to a disk file
2. Deserialize
To recover an object from a disk file is to convert the binary into an object
package test2;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/ * * *@author gzh
* @createTime 2021/12/6 8:08 PM
*/
public class Test {
public static void main(String[] args) throws Exception {
testversion1L();
}
public static void testversion1L(a) throws Exception {
File file = new File("Person.out");
/ / the serialization
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
Person Person = new Person("Haozi".22."Shanghai");
oout.writeObject(Person); // Convert objects to binary (that is, byte arrays) and write to disk files
oout.close();
// deserialize
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object newPerson2 = oin.readObject();// To recover an object from a disk file, convert the binary to an objectoin.close(); System.out.println(newPerson2); }}Copy the code
What does Java serialization do? What is the protocol format?
The main and essential thing is to convert objects to binary.
Formats are Java’s unique set of things, including:
- Java base data types
- Java non-base data types
So, in short, Java serialized binaries are only recognized by the Java language itself, that is, only the Java language itself can convert the binaries back to objects.
Source code analysis reference:
Juejin. Cn/post / 703916…
What exactly does a serialized ID do?
JDK API official documentation
Serializable
If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification.
However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization.
Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value.
It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class–serialVersionUID fields are not useful as inherited members.
conclusion
Serialization ID, essentially the version number of the POJO class.
- If the serialization ID is not explicitly written, the JVM defaults to generating a random value.
- And if you do, in general, it’s worth 1 liter.
Generating a large random value with IDEA is also possible.
- The official recommendation is to display a single value (generally 1L is fine) — because JVM implementations may differ, resulting in different version numbers generated by the consumer and provider JVMS.
- If the provider adds a new field or changes the field type, it needs to upgrade the serialization ID version, which will cause an exception when the provider deserializes: the consumer and provider have different serialization ID versions. This is exactly what we want — because if the provider adds a new field or changes the field type, but the provider doesn’t upgrade the serialization ID version, it’s still the same as the provider’s version, then the provider deserializes without exception, but at this point, The value of the provider’s new field is NULL (the provider added a field) or a cast exception (the provider changed the field type).
demo
Following the above demo code example, I’ll show you again here.
Start by adding a field -email to the provider’s POJO class.
package test;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 2L;
private String name;
private Integer age;
private String address;
private String email; // Add a field
public Person(a) {}public Person(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public Person(String name, Integer age, String address,String email) {
this.name = name;
this.age = age;
this.address = address;
this.email = email;
}
@Override
public String toString(a) {
return "Person{" +
"name='" + name + ' '' + ", age=" + age + ", address='" + address + ''' + ", email='" + email + '' ' +
'} '; }}Copy the code
Then, deserialize the object directly from the files generated by the demo above. Note that the serialization code is commented out.
package test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import test2.Person;
/ * * *@author gzh
* @createTime 2021/12/6 8:08 PM
*/
public class Test {
public static void main(String[] args) throws Exception {
testversion1L();
}
public static void testversion1L(a) throws Exception {
File file = new File("Person.out");
/ / the serialization
// ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
// Person Person2 = new Person("Haozi", 22, "Shanghai ");
// oout.writeObject(Person2);
// oout.close();
// deserialize
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object newPerson2 = oin.readObject(); // Get the objectoin.close(); System.out.println(newPerson2); }}Copy the code
Print result:
Person{name='Haozi', age=22, address=' Haozi', email='null'}Copy the code
Description:
- The provider must also have a corresponding POJO class
From the print, deserialization directly from disk files can produce objects, provided that the provider also has poJO classes with the same package and class name before the provider can create objects.
If the provider does not have a corresponding POJO class, an error is reported: no class exception was found.
Exception in thread "main" java.lang.ClassNotFoundException: Person
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:686)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1868)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at Test.testversion1L(Test.java:27)
at Test.main(Test.java:14)
Copy the code
How do you simulate this exception? Just drop the POJO class or change its name.
- Why is the value of the provider’s new field null?
If the provider has a corresponding POJO class, the provider can create an object from the binary data of the disk file, as printed above. However, because the provider added a field that was not present when the consumer wrote the binary data to the disk file, the value of the new field is NULL.
However, in practical work, we should not allow this kind of situation to appear. If the provider adds a field, the POJO class needs to be updated to change the serialization ID to 2L.
Moving on, as in the example above, when the consumer writes binary data to disk at version 1L, provider deserialization will report an error: version numbers do not match, specifically, stream (consumer) version 1 and local (provider) version 2.
Exception in thread "main" java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at Test.testversion1L(Test.java:27)
at Test.main(Test.java:14)
Disconnected from the target VM, address: '127.0.0.1:0', transport: 'socket'
Process finished with exit code 1
Copy the code
It was unusual at the time, but it was exactly what we wanted. Since it is obvious that the poJO versions of the consumer and provider do not match, the essence behind this must be that the provider has added a field or changed the field type. At this point, the consumer should also add or modify the field type to be consistent with the provider. If the provider does not update the version number, the provider does not report an error, but the value of the new field added by the provider is null(because the consumer is missing the new field), this is actually a problem, because the provider normally added the field, must be used by the value of the field. Where does the value come from? It must be the consumer. How do consumers come? New fields are also added and are consistent with the provider version to solve the problem.
reference
www.liaoxuefeng.com/wiki/125259…
What does the Java serialVersionUID do? www.jianshu.com/p/91fa3d2ac…