. Consider using a custom serialization form:
Designing a serialization form of a class is equally important as designing an API for this class. Therefore, do not rashly use the default serialization behavior before seriously considering whether the default serialization form is appropriate. Before making a decision, you need to examine the encoding format from the flexibility, performance, and correctness perspectives. Generally, you can accept the default serialization format only when the custom serialization format you designed is basically the same as the default format. For example, when an object's physical representation is equivalent to its logical content, it may be suitable for using the default serialization form. See belowCodeExample:
1Public ClassNameImplementsSerializable {
2Private FinalString lastname;
3Private FinalString firstname;
4Private FinalString middlename;
5......
6}
Logically, the three fields of the class accurately reflect their logical content. However, sometimes, even if the default serialization form is appropriate, a readobject method must be provided to ensure the constraints and security. In the above Code, firstname and lastname cannot be null.
Let's look at an extreme example:
1 Public Final Class Stringlist Implements Serializable {
2 Private Int Size = 0;
3 Private Entry head = Null ;
4 Private Static Class Entry Implements Serializable {
5 String data;
6 Entry next;
7 Entry previous;
8 }
9 }
If the above sample code is serialized in the default format, the data of each node in the two-way linked list and the frontend and backend relationships are serialized. Therefore, when this physical representation differs substantially from its logical data content, the use of the default serialization form has the following Disadvantages:
1. It makes the export API of this class always bound to the internal representation of the class. Even if a better implementation method is found in the future, the original implementation method cannot be removed.
2. It consumes too much space. In fact, for the above sample code, we only need to serialize the data part, and the relationship between linked list nodes can be completely ignored.
3. It will consume too much time.
4. It will cause stack overflow.
Based on the above four points, we have revised the serialization implementation method of the stringlist class, as shown in the following code:
1 Public Final Class Stringlist Implements Serializable {
2 Private Transient Int Size = 0;
3 Private Transient Entry head = Null ;
4 Private Static Class Entry {
5 String data;
6 Entry next;
7 Entry previous;
8 }
9 Private Void Writeobject (objectoutputstream S) Throws Ioexception {
10 S. defaultwriteobject ();
11 S. writeint (size );
12 For (Entry E = head; e! = Null ; E = E. Next)
13 S. writeobject (E. data );
14 }
15 Private Void Readobject (objectinputstream S)
16 Throws Ioexception, classnotfoundexception {
17 S. defaultreadobject ();
18 Int Numelemnet = S. readint ();
19 For (Int I = 0; I <numelements; I ++)
20 Add (string) S. readobject ());
21 }
22 Public Final Void Add (string s ){...}
23 ......
24 }
In the revised Code, all domain fields are transient, but the first task of the writeobject and readobject methods is to call the defaultwriteobject and defaultreadobject methods first, even if this is not necessary for the default serialization form. In future modifications, it is very likely that non-transient domain fields will be added to the class. If you forget to synchronously modify the writeobject or readobject method, the serialization and deserialization data processing methods will be inconsistent.
If one or more domain fields are marked as transient, these domain fields are initialized to their default type. For example, if the object reference field is set to null, the default value of the basic numeric field is 0, and the default value of the Boolean field is false. If these values cannot be accepted by any transient field, you must provide a readobject method. It first calls defaultreadobject and then restores these transient fields to an acceptable value.
It should be noted that whether or not you use the default serialization form, if you force any synchronization on any other method that reads the entire object state, this synchronization must also be enforced on Object serialization. See the following code:
1Private Synchronized VoidWriteobject (objectoutputstream S)ThrowsIoexception {
2S. defaultwriteobject ();
3}
. The readobject write method is as follows:
An Immutable date range class is introduced in entry 39, which contains a variable private date field. This class copies the date object in its constructor and access method to maintain its constraints and non-variability. See the following code:
1 Public Final Class Period {
2 Private Final Date start;
3 Private Final Date end;
4 Public Period (date start, date end ){
5 This . Start = New Date (start. gettime ());
6 This . End = New Date (end. gettime ());
7 If ( This . Start. compareto ( This . End)> 0)
8 Throw New Illegalargumentexception ();
9 }
10 Public Date start (){
11 Return New Date (start. gettime ());
12 }
13 Public Date end (){
14 Return New Date (end. gettime ());
15 }
16 Public String tostring (){
17 Return Start + "-" + end;
18 }
19 ......
20 }
The physical representation of this object exactly matches with its logical representation, so we can use the default serialization form. Therefore, add "implements serializable" to declare the class ". However, if you do, this class will no longer guarantee its key constraints.
The problem is that if the deserialization Data Source comes from the normal serialization of this type of instance, it will not cause any problems. If the opposite is true, the deserialization Data Source comes from a set of forged data streams. In fact, the deserialization mechanism is to instantiate a specified object from a set of rule-based data streams, therefore, we will have to face the danger that the internal constraints of the period instance object will be damaged. See the following code:
1 Public Class Bogusperiod {
2 Private Static Final Byte [] Serializedform = New Byte [] {
3 ...... // Here is a set of forged byte stream data
4 };
5 Public Static Void Main (string [] ARGs )[
6 Period P = (period) deserialize (serializedform );
7 System. Out. println (P );
8 }
9 Private Static Object deserialize ( Byte [] SF ){
10 Try {
11 Inputstream is = New Bytearrayinputstream (SF );
12 Objectinputstream OIS = New Objectinputstream (is );
13 Return Ois. readobject ();
14 } Catch (Exception e ){
15 Throw New Illegalargumentexception (E );
16 }
17 }
18 }
If you execute the above Code, you will find that the period constraint is broken, and the end date is earlier than start. To solve this problem, you can provide a readobject Method for period. This method first calls defaultreadobject and then checks the validity of the object after deserialization. If the check fails, an invalidobjectexception exception is thrown, so that the deserialization process cannot be completed successfully.
1Private VoidReadobject (objectinputstream S)
2ThrowsIoexception, classnotfoundexception {
3S. defaultreadobject ();
4If(Start. compareto (end)> 0)
5Throw NewInvalidobjectexception (start + "after" + end );
6}
If you execute the above Code, you will find that the period constraint is broken, and the end date is earlier than start. To solve this problem, you can provide a readobject Method for period. This method first calls defaultreadobject and then checks the validity of the object after deserialization. If the check fails, an invalidobjectexception exception is thrown, so that the deserialization process cannot be completed successfully.
1Private VoidReadobject (objectinputstream S)
2ThrowsIoexception, classnotfoundexception {
3S. defaultreadobject ();
4If(Start. compareto (end)> 0)
5Throw NewInvalidobjectexception (start + "after" + end );
6}
In addition to the above attack methods, there is also a more concealed attack method, which is also used to defraud the deserialization method of trust by forging serialized data streams. During data forgery, the private domain field reference is stored externally, so that after the object instance deserialization is successful, the external operation can still operate its internal data, so the risk still exists. How can we avoid this risk? See the following revised readobject method:
1 Private Void Readobject (objectinputstream S)
2 Throws Ioexception, classnotfoundexception {
3 S. defaultreadobject ();
4 // Execute protective copy
5 Start = New Date (start. gettime ());
6 End = New Date (end. gettime ());
7 If (Start. compareto (end)> 0)
8 Throw New Invalidobjectexception (start + "after" + end );
9 }
note that protective copy must be performed before the validity check.
a basic rule is provided here to help determine whether the default readobject method is acceptable. A rule is to add a public constructor whose parameters correspond to each non-transient field in the object, regardless of the parameter value, they can be saved to the corresponding domain without checking. If this method is acceptable, the default readobject method is reasonable. Otherwise, an explicit readobject method is required.
for non-final serializable classes, there are other similarities between the readobject method and the constructor. The readobject method cannot call override methods, it cannot be called directly or indirectly. If this rule is violated and the method is overwritten, the overwritten method runs before the subclass status is deserialized. Program may fail.