Talk about serialization in J2SE (3)
Author: Favo yang
Favoyang@yahoo.com
When to accept the default Java serialization behavior
First of all, we must learn about Java default serialization behavior. Java saves everything about objects, that is, some don't need to be saved and saved. In general, we just need to save logic data. We can use keyword transient to marke with keyword transient.
The following is an example:
Import java.io. *;
Public Class Serial Implements Serializable {
INT Company_ID;
String Company_addr;
Transient Boolean Company_FLAG;
}
The company_flag field will not participate in serialization and deserialization, but you also increased the responsibility for his initial value. This is also one of the problems that are often caused by serialization. Because serialization is equivalent to a PUBLIC constructor that only accepts data streams, this object constructing method is outside language. But he is still a form of constructor. If your class cannot guarantee initialization through other aspects, you need an additional REDOBJECT method, first normal deserialization, and then initialize the field indicated by the Transient.
When it is not suitable, using Java default serialized behavior may bring speed, the worst case, may result in overflow. In the implementation of certain data structures, it is often filled with various cyclic references, while Java's default serialization behavior does not understand your object structure, its result is that Java tries to pass a expensive "map traversal" Save the object status. It is conceivable that not only slow and may overflow. At this time you have to provide your own ReadObject, instead of the default behavior.
Compatibility problem
Compatibility has always been a complicated and troubleshoot problem.
Don't compatibility:
Let's first see if our purpose is to compatibility, what should be paid attention to. There are not compatibility, such as WAR3, whenever the version upgrade is unable to read the previous Replays.
Compatible is also version control, Java is controlled by one named UID (Stream Unique Identifier), this UID is implicit, it has been calculated by many factors such as class name, method name, theoretically, theoretical relationship , That is, unique. If the UID is different, the reverse sequence cannot be achieved, and the InvalidClassexception will be obtained.
When we want to produce a new version (realization is not changed), and abandon the previous version, you can implement it by explicitly name UID:
PRIVATE Static Final long serialversion = ????;
You can make a version number, but pay attention not to repeat. This will get InvalidClassexception when it is reverse selecinstened, and we can capture this exception at the old version and prompt users to upgrade new versions.
When the change is not large, maintain compatibility (one special case of downward compatibility):
Sometimes your class has added some irrelevant non-privacy methods, while the logical field does not change, you certainly hope that the old version and the new version keep compatibility, the method is also achieved by an explicit denominator UID. Let's verify it.
old version:
Import java.io. *;
Public Class Serial Implements Serializable {
INT Company_ID;
String Company_addr;
Public serial1 (int company_id, string company_addr) {this.company_id = Company_ID;
THIS.COMPANY_ADDR = Company_ADDR;
}
Public string toString () {
Return "Data:" Company_ID "" "
COMPANY_ADDR;
}
}
new version
Import java.io. *;
Public Class Serial Implements Serializable {
INT Company_ID;
String Company_addr;
Public serial1 (int company_id, string company_addr) {
This.Company_ID = Company_ID;
THIS.COMPANY_ADDR = Company_ADDR;
}
Public string toString () {
Return "Data:" Company_ID "" "
COMPANY_ADDR;
}
Public void todo () {} // insignificant method
}
First serialize the old version, then read it with a new version, an error occurred:
Java.io.invalidclassexception: serial.serial1; local class incompatible: stream classdesc serialversion = 762508508425139227, local class serialversionuid = 1187169935661445676
Next we join the explicit denominator UID:
Download
Run again, successfully eaten new objects
Data: 1001 COM1
How to keep up compatibility:
Upward compatibility means that the elder version can read a new version serialized data stream. Often appearing in our server update, still hoping that the old client can support the reverse sequence of new data streams until it updates to the new version. It can be said that this is a semi-automatic thing.
With general speaking, because SerialVersionuid in Java is the only flag that controls whether the reverse sequence is successful, as long as this value is different, it cannot be reversed successfully. However, as long as this value is the same, the reverse sequence is used, in which the excess content in the new data stream will be ignored for upward compatibility; the old data stream is in the old data stream. All content contained will be recovered, and the parts that are not involved in the new version will remain default. Using this feature, it can be said that as long as we think that the SerialVersionuid is constant, upward compatibility is automated.
Of course, one but we take the old content in the new version, the situation is different, even if the UID remains unchanged, it will trigger an exception. It is precisely because of this, we must keep in mind that once the serialization is to keep up and down, you can't change it casually! ! !
Test also proved this, interested readers can try itselves.
How to keep down compatibility:
As mentioned above, you will be of course thinking that as long as the SerialVersionUID is constant, the downward compatibility is automatically implemented. But in fact, it is complicated down. This is because we must be responsible for those that do not have initialization. To ensure they can be used. So you must use
Private void readObject (java.io.objectInputStream in)
THROWS IEXCEPTION, ClassNotFoundException {
In.DefaultReadObject (); // Pre-sequenced object
IF (ver = 5552) {// Previous version 5552
... Initialize other fields
} else if (ver = 5550) {// Previous version 5550
... Initialize other fields
} Else {// too old version does not support
Throw new invalidclassexception ();
}
}
The careful readers will notice to ensure that IN.DEFAULTREADOBJECT (); can be implemented smoothly, you must ask SerialVersionuID to be consistent, so the VER here is not able to take the serialversionuid. The VER here is a pre-plug-in Final LONG VER = XXXX; and it cannot be modified by Transient. So keeping downward compatibility at least three points:
N serialversionuid
n Pre-inserts our own version of the identifier sign of Final Long Ver = XXXX;
n guarantees that all domains are initialized
Discuss compatibility strategies:
Here we can see that the compatibility to keep down is very troublesome. And as the number of versions increases. Maintenance will become difficult and cumbersome. What kind of program is discussed how the compatibility serialization strategy has exceeded the category of this article, but for a game's storage function, and the requirements for the compatibility of the document for a word processing software are certainly different. For the storage function of the RPG game, it is generally required to be kept downward, where using Java serialization methods can be prepared according to the three points of the above analysis. The use of the object serialization method is still possible for such a situation. The compatibility requirements for documents for a word processing software are quite high. The policies in general are required to be good demo compatibility, and as much as possible. Compatibility. It is generally not used to use object serialization techniques, a well-designed document structure to solve the problem.
Data consistency problem, constraint
To know that serialization is another form of "public constructor", but he only constructs an object, not any inspection, this is very uncomfortable, so the necessary check is necessary, this takes readObject ()
Private void readObject (java.io.objectInputStream in)
THROWS IEXCEPTION, ClassNotFoundException {
In.DefaultReadObject (); // Pre-sequenced object
... conduct inspection and initialization
}
For structural considerations, a function named initialize is usually used, which is responsible for checking and initialization, if the failure throws an exception. To keep inspections and initialization is easy to be forgotten, this often leads to problems. Another problem is that when the parent class does not join ReadObject (), the subclasses easily forget to call the corresponding Initialize function. This seems to have returned why the problem is to introduce the constructor, which is to prevent subclasses from forgetting to call the initialization function to trigger various issues. So, if you want to keep the data consistency, be sure to join ReadObject ().
Security Question
The topic of security is beyond the category of this article, but you should know that there may be an attacker to prepare a malicious data stream enterprise for your class to generate an error class. When you need to make sure your object data is secure, you can usually use the above method to check and initialize, but for some references. The solution is to protect important parts for important components. Here is a good way to copy individual domains without protective copying, but direct protective copying the entire object. This is: Object Readresolve () throws ObjectStreamException;
The use of this method is that he will be recall next to ReadObject (). It will use the returned object instead of the original deserialized object. That is, the original readObject () reverse sequence object will be discarded immediately.
Object readresolve () throws ObjectStreamException {
Return New Serial2 (this.xxx1, this.xxx2); // xxx1, xxx2 is just in-serialization, this is a protective copy
}
In this way, although there is a waste of time, this method can be used for a particularly important and secure class. If data consistency issues, constraints can be solved by checking one by one, but also use this method, but should consider good costs, and pay attention to the limitations below.
With readResolve () has a significant shortcoming, it is to achieve readResolve (), and subclasses will become unstoppable. If a protected or public parent class readresolve () exists, and the subclass has not rewritten it, it will eventually get a parent class when the subclass is reversed, which is neither we want. It is not easy to find this error. And let the child rewrite ReadResolve () is undoubtedly a burden. That is to say, for the case to be inherited, implementing readResolve () to protect classes is not a good way. We can only write a protective readObject () using the first method.
So my suggestion is: In general, only the class readResolve () is used to protect it with the class of Final.