Document directory
- [Introduction]
- [Serialversionuid problem]
- Static variable sequence]
- [Serialization of parent class and transient keyword]
- Encrypt sensitive fields]
- [Serialization storage rules]
[Introduction]
Java serialization technology that serializes Java objects into binary files is an important technical point in the Java series. In most cases, developers only need to understand that the serialized class must implement the serializable interface, and use objectinputstream and objectoutputstream to read and write objects. However, in some cases, it is far from enough to know this. The article lists some real situations encountered by the author, which are related to Java serialization. By analyzing the causes of the situation, this makes it easy for readers to remember some advanced knowledge in Java serialization.
[Serialversionuid problem]
In Java serialization and deserialization, whether the virtual machine allows deserialization depends not only on whether the class path and functional code are consistent, one important thing is whether the serialization IDs of the two classes are consistent (that is, Private Static final long serialversionuid = 1l). If the serialversionuid is different, you will get a java. io. invalidclassexception:
Package Wen. hui. test. serializable; import Java. io. serializable;/*** serializable test ** @ author whwang * 09:50:07 */public class A implements serializable {Private Static final long serialversionuid = 2L; Public () {} public void print () {system. err. println ("test serializable");} public static void main (string [] ARGs) throws exception {}}
Package Wen. hui. test. serializable; import Java. io. fileinputstream; import Java. io. fileoutputstream; import Java. io. objectinputstream; import Java. io. objectoutputstream;/***** @ author whwang * 09:54:36 */public class test1 {public static void main (string [] ARGs) throws exception {// write object string filename = "OBJ"; towrite (filename); // read object toread (filename);} public static void towrite (string filename) throws exception {objectoutputstream OOS = new objectoutputstream (New fileoutputstream (filename); OOS. writeobject (new A (); OOS. close ();} public static void toread (string filename) throws exception {objectinputstream OIS = new objectinputstream (New fileinputstream ("OBJ"); a t = (a) Ois. readobject (); T. print (); ois. close ();}}
1. Run the main method of test1 directly and run it correctly;
2. Comment out toread (filename) in main method of test1, change the value of serialversionuid in Class A to 1, and run test1. Then, enable toread (filename) on behalf of towrite (filename) note: At the same time, change the value of serialversionuid in Class A to 2. Run test1 and an exception is thrown. This indicates that if the serialversionuid is different, deserialization cannot be performed even if the two classes are "identical.
Exception in thread "main" java.io.InvalidClassException: wen.hui.test.serializable.A; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
Serialization ID provides two generation policies in eclipse. One is fixed 1l, and the other is random generation of a non-repeated long type data (actually generated using JDK ), we recommend that you use the default 1l if there is no special requirement. This ensures that deserialization is successful when the code is consistent. So what is the function of the randomly generated serialization ID? Sometimes, by changing the serialization ID, it can be used to limit the usage of some users. For example, in the facade mode, the client can interact with the business logic object through the fa-ade object. The fa c Ade object on the client cannot be directly composed
The client is generated, but the server needs to be generated. After serialization, the binary object data is transmitted to the client through the network. The client is responsible for deserializing and obtaining the fa c object. This mode allows the use of client programs to be permitted by the server. At the same time, the fa c Ade object classes on the client and server must be consistent. To update the server version, you only need to generate the serialization ID of the server's fa C Ade object class again, and the client will fail to deserialize the fa c Ade object, that is, force the client to obtain the latest program from the server.
Static variable sequence]
View the Code directly:
Package Wen. hui. test. serializable; import Java. io. serializable;/*** serializable test ** @ author whwang * 09:50:07 */public class A implements serializable {Private Static final long serialversionuid = 1l; public static int staticvar = 10; public A () {} public void print () {system. err. println ("test serializable");} public static void main (string [] ARGs) throws exception {}}
Package Wen. hui. test. serializable; import Java. io. fileinputstream; import Java. io. filenotfoundexception; import Java. io. fileoutputstream; import Java. io. ioexception; import Java. io. objectinputstream; import Java. io. objectoutputstream;/*** serializes and stores the object state, and static variables belong to the class state, will not be serialized * @ author whwang * 2011-12-1 10:12:06 */public class Test2 {public static void main (string [] ARGs) {try {// initially, staticvar is 10 objectoutputstream out = new objectoutputstream (New fileoutputstream ("OBJ"); out. writeobject (new A (); out. close (); // After serialization, change it to 100. staticvar = 100; objectinputstream oin = new objectinputstream (New fileinputstream ("OBJ"); a t = (a) oin. readobject (); oin. close (); // read again, through T. staticvar prints the new value system. err. println (T. staticvar);} catch (filenotfoundexception e) {e. printstacktrace ();} catch (ioexception e) {e. printstacktrace ();} catch (classnotfoundexception e) {e. printstacktrace ();}}}
Class A static field staticvar initialization value is 10, in the main method of teste2, serialize an instance of Class A to the hard disk, and then modify the static field staticvar = 100, then, deserialize the just-serialized object and output the "staticvar" value of the object. Is the output 100 or 10?
The result output is 100. The reason for printing 100 is that static variables are not saved during serialization, which is easy to understand. serialization stores the object state and static variables are in the class state, therefore, serialization does not save static variables.
[Serialization of parent class and transient keyword]
Situation: a subclass implements the serializable interface. Its parent class does not implement the serializable interface. It serializes the subclass object and then deserializes it to output the value of a variable defined by the parent class, the variable value is different from the value during serialization.
Solution: to serialize the parent class object, you need to make the parent class also implement the serializable interface. If the parent class is not implemented, a default no-argument constructor is required. When the parent class does not implement the serializable interface, the virtual machine will not serialize the parent object, and the construction of a Java object must first have a parent object to have sub-objects. deserialization is no exception. In deserialization, in order to construct the parent object, only the non-argument constructor of the parent class can be called as the default parent object. Therefore, when we take the variable value of the parent object, its value is the value after the constructor of the parent class is called. If you consider this serialization, initialize the variables in the non-argument constructor of the parent class. Otherwise, the values of the parent class variables are declared by default, as shown in
The default value of int type is 0, and that of string type is null.
The transient keyword is used to control variable serialization. Adding this keyword before the variable declaration can prevent the variable from being serialized into the file. After deserialization, the value of the transient variable is set to the initial value. For example, if the int type is 0, the object type is null.
Package Wen. hui. test. serializable;/***** @ author whwang * 2011-12-1 10:23:10 */public class B {public int B1; Public int B2; Public B () {This. b2= 100 ;}}
Package Wen. hui. test. serializable; import Java. io. serializable;/***** @ author whwang * 2011-12-1 09:49:51 */public class C extends B implements serializable {Private Static final long serialversionuid = 1l; Public int C1; Public int C2; public C () {// assign this to B1 and B2. b1 = 1; this. b2 = 2; this. c2 =-100 ;}}
Package Wen. hui. test. serializable; import Java. io. fileinputstream; import Java. io. filenotfoundexception; import Java. io. fileoutputstream; import Java. io. ioexception; import Java. io. objectinputstream; import Java. io. objectoutputstream;/*** if the parent class does not implement serializable, the parent class will not be serialized. When the Child class is deserialized, * the constructor without parameters of the parent class will be called. * @ Author whwang * 2011-12-1 10:23:51 */public class test3 {public static void main (string [] ARGs) {try {objectoutputstream out = new objectoutputstream (New fileoutputstream ("OBJ"); out. writeobject (New C (); out. close (); objectinputstream oin = new objectinputstream (New fileinputstream ("OBJ"); C t = (c) oin. readobject (); oin. close (); system. err. println (T. b1 + "," + T. b2 + "," + T. c1 + "," + T. c2);} catch (filenotfoundexception e) {e. printstacktrace ();} catch (ioexception e) {e. printstacktrace ();} catch (classnotfoundexception e) {e. printstacktrace ();}}}
Run the main method of test3, and the output result is 0,100, 0,-100. That is, the initialization of the member variables of the parent class is not serialized in the subclass constructor; but in the deserialization, it is used to call the non-argument constructor of the parent class to instantiate the parent class.
Encrypt sensitive fields]
Situation: the server sends the serialized object data to the client. Some data in the object is sensitive, such as the password string. You want to encrypt this field during serialization, if the client has a decryption key, the password can be read only when the client is deserialized. This ensures the data security of the serialized object to a certain extent.
Solution: During the serialization process, the virtual opportunity attempts to call the writeobject (objectoutputstread out) and readobject (objectinputstread in) methods in the object class (through the reflection mechanism ), if this method is not available, the defaultwriteobject method of objectoutputstream and the defaultreadobject method of objectinputstream are called by default. Custom writeobject and readobject
This method allows you to control the serialization process. For example, you can dynamically change the serialization value during the serialization process. Based on this principle, it can be used in practical applications for encryption of sensitive fields, such:
Package Wen. hui. test. serializable; import Java. io. ioexception; import Java. io. objectinputstream; import Java. io. objectinputstream. getfield; import Java. io. serializable;/*** @ author whwang 2011-12-1 10:29:54 */public class D implements serializable {Private Static final long serialversionuid = 1l; private string password; Public D () {} Public String GetPassword () {return password;} public void setpassword (string password) {This. password = password;} // Private void writeobject (objectoutputstream out) {//} private void readobject (objectinputstream in) {try {getfield readfields = in. readfields (); object = readfields. get ("password", ""); system. err. println ("string to be decrypted:" + object. tostring (); Password = "password"; // simulate decryption and obtain the local key} catch (ioexception e) {e. printstacktrace ();} catch (classnotfoundexception e) {e. printstacktrace ();}}}
Package Wen. hui. test. serializable; import Java. io. fileinputstream; import Java. io. filenotfoundexception; import Java. io. fileoutputstream; import Java. io. ioexception; import Java. io. objectinputstream; import Java. io. objectoutputstream;/*** @ classname: test4 * @ Description: encryption test. * @ Author whwang * @ date 2011-12-1 05:01:34 */public class test4 {public static void main (string [] ARGs) {try {objectoutputstream out = new objectoutputstream (New fileoutputstream ("OBJ"); D T1 = new D (); t1.setpassword ("encryption "); // encrypted (simulated) out. writeobject (T1); out. close (); objectinputstream oin = new objectinputstream (New fileinputstream ("OBJ"); d t = (d) oin. readobject (); oin. close (); system. err. println ("decrypted string:" + T. getPassword ();} catch (filenotfoundexception e) {e. printstacktrace ();} catch (ioexception e) {e. printstacktrace ();} catch (classnotfoundexception e) {e. printstacktrace ();}}}
Before serialization, encrypt the password field and then serialize it to the hard disk. In the anti-serialization process, perform decryption through the readobject (objectinputstream in) in Class D to ensure data security.
For example, the RMI technology is completely based on the Java serialization technology, and the parameter objects required for server interface calls come from clients, which are transmitted over the network. This involves the secure transmission of RMI. Some sensitive fields, such as user name and password (the user needs to transmit the password during login), we want to encrypt it. At this time, you can use the methods described in this section to encrypt the password on the client and decrypt the password on the server to ensure the security of data transmission.
[Serialization storage rules]
See the following code:
Package Wen. hui. test. serializable; import Java. io. file; import Java. io. fileinputstream; import Java. io. filenotfoundexception; import Java. io. fileoutputstream; import Java. io. ioexception; import Java. io. objectinputstream; import Java. io. objectoutputstream;/***** @ author whwang 2011-12-1 11:00:35 */public class test5 {public static void main (string [] ARGs) {objectoutputstream out; try {out = new objectoutputstream (New fileoutputstream ("OBJ"); a t = new A (); // try to write the object into the file out twice. writeobject (t); out. flush (); system. err. println ("added to the first class:" + new file ("OBJ "). length (); // T. A = 10; out. writeobject (t); out. close (); system. err. println ("added to the second class:" + new file ("OBJ "). length (); objectinputstream oin = new objectinputstream (New fileinputstream ("OBJ"); // read the two files a T1 = (a) oin in sequence. readobject (); A t2 = (a) oin. readobject (); oin. close (); // determine whether two references point to the same object system. err. println (T1 = t2);} catch (filenotfoundexception e) {e. printstacktrace ();} catch (ioexception e) {e. printstacktrace ();} catch (classnotfoundexception e) {e. printstacktrace ();}}}
Write files to the same object twice, print the storage size after the object is written and the storage size after the two writes, and then deserialize the two objects from the file, compare whether the two objects are the same object. The general idea is that when two objects are written, the file size will change to twice. During deserialization, two objects are generated due to reading from the file, when the values are equal, it should be true if the values are false. But the actual result is: The second write object only adds 5 bytes, and the two objects are equal. Why?
A: The Java serialization mechanism has specific storage rules to save disk space. When the same object is written to a file (based on package name + class name ), the object content will not be stored, but a reference will be stored again. The 5-byte storage space added above is the space for adding references and some control information. During deserialization, the reference relationship is restored so that t1 and t2 in the program point to a unique object. The two are equal and the output is true. This storage rule greatly saves storage space.
Note that the comments/t. A = 10 are opened in the above program, and the execution results are the same. The reason is that after the object is written for the first time, when the second attempt is made, the VM will know that there is already an identical object written into the file based on the reference relationship, so it will only save the reference written for the second time, therefore, the object is saved for the first time.