一、序列化
java序列化提供了一個框架,用來將對象編碼成字節(jié)流,并從字節(jié)流編碼中重新構(gòu)建的對象。將對象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對象稱之為反序列化。java序列為對象的可持久化及遠(yuǎn)程共享提供了一種簡單機(jī)制。它實現(xiàn)起來非常方便,只需要實現(xiàn)serializble接口即可。但往往表面的上簡單,隱藏了背后巨大的風(fēng)險,如果你不了解serializable請慎用,因為其中有太多坑,且當(dāng)你遇到時可能會不知道所措。effective java在序列化一章第一條就提出“謹(jǐn)慎地實現(xiàn)serializable接口”,可見serializable接口背后實現(xiàn)可能隱藏著“坑人的秘密”。
本文參考了網(wǎng)上大量的技術(shù)文章和effective java,將從序列化的原理、注意事項及實際應(yīng)用幾個方面,通過實例來揭開java序列化的面紗。
在這里補(bǔ)充研究序列化的背景:有一個Object持久化于緩存中,經(jīng)常需要變更字段(添加或刪除),每次做變更就要更改緩存表(擔(dān)心不兼容帶來問題,一直不確定哪些變更會來問題或引起什么樣的問題),我希望實現(xiàn)一種序列化,當(dāng)變更或刪除字段時不需要變更緩存表,這需要達(dá)到兩個目的:1、新的類訪問舊的緩存時沒問題;2.舊的類訪問新的緩存時也沒問題。這個問題雖然在我的需求背景之下得到了快速解決,但還是希望將序列化給出一個充分研究,以備后續(xù)信手拈來。
二、序列化實現(xiàn)方式
2.1 簡單示例
一個Person類,具有兩個屬性:name和age;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
生成一個Person的實例p,將期通過ObjectOutputStream寫入文件,并通過ObjectInputStream讀出來。這是一個完整的序列化/反序列化過程:ObjectOutputStream將p轉(zhuǎn)化成字節(jié)流寫入文件,ObjectInputStream將從文件中讀出的字節(jié)流重新創(chuàng)建newPerson實例。
@Test
public void testSerializable() throws Exception {
File file = new File("p.dat");
Person p = new Person("xiaoming", 10);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(p);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Object newPerson = ois.readObject();
ois.close();
System.out.println(newPerson);
}
通過上面的過程,我們可以看出默認(rèn)的序列化機(jī)制對使用者而言是非常簡單的。序列化具體的實現(xiàn)是由ObjectOutputStream完成的;反序列化的具體實現(xiàn)是由ObjectInputStream完成的。那接下來我們就看一下它們具體做了什么事
2.2 Serializable
在介紹具體實現(xiàn)之前,我們先來看一下Serializable接口,這畢竟是默認(rèn)情況下的,使用者看到的唯一的東西。
/**
* Serializability of a class is enabled by the class implementing the
* java.io.Serializable interface. Classes that do not implement this
* interface will not have any of their state serialized or
* deserialized. All subtypes of a serializable class are themselves
* serializable. The serialization interface has no methods or fields
* and serves only to identify the semantics of being serializable. <p>
*
* To allow subtypes of non-serializable classes to be serialized, the
* subtype may assume responsibility for saving and restoring the
* state of the supertype's public, protected, and (if accessible)
* package fields. The subtype may assume this responsibility only if
* the class it extends has an accessible no-arg constructor to
* initialize the class's state. It is an error to declare a class
* Serializable if this is not the case. The error will be detected at
* runtime. <p>
*
* During deserialization, the fields of non-serializable classes will
* be initialized using the public or protected no-arg constructor of
* the class. A no-arg constructor must be accessible to the subclass
* that is serializable. The fields of serializable subclasses will
* be restored from the stream. <p>
*
* When traversing a graph, an object may be encountered that does not
* support the Serializable interface. In this case the
* NotSerializableException will be thrown and will identify the class
* of the non-serializable object. <p>
*
* Classes that require special handling during the serialization and
* deserialization process must implement special methods with these exact
* signatures:
*
* <PRE>
* private void writeObject(java.io.ObjectOutputStream out)
* throws IOException
* private void readObject(java.io.ObjectInputStream in)
* throws IOException, ClassNotFoundException;
* private void readObjectNoData()
* throws ObjectStreamException;
* </PRE>
*
* <p>The writeObject method is responsible for writing the state of the
* object for its particular class so that the corresponding
* readObject method can restore it. The default mechanism for saving
* the Object's fields can be invoked by calling
* out.defaultWriteObject. The method does not need to concern
* itself with the state belonging to its superclasses or subclasses.
* State is saved by writing the individual fields to the
* ObjectOutputStream using the writeObject method or by using the
* methods for primitive data types supported by DataOutput.
*
* <p>The readObject method is responsible for reading from the stream and
* restoring the classes fields. It may call in.defaultReadObject to invoke
* the default mechanism for restoring the object's non-static and
* non-transient fields. The defaultReadObject method uses information in
* the stream to assign the fields of the object saved in the stream with the
* correspondingly named fields in the current object. This handles the case
* when the class has evolved to add new fields. The method does not need to
* concern itself with the state belonging to its superclasses or subclasses.
* State is saved by writing the individual fields to the
* ObjectOutputStream using the writeObject method or by using the
* methods for primitive data types supported by DataOutput.
*
* <p>The readObjectNoData method is responsible for initializing the state of
* the object for its particular class in the event that the serialization
* stream does not list the given class as a superclass of the object being
* deserialized. This may occur in cases where the receiving party uses a
* different version of the deserialized instance's class than the sending
* party, and the receiver's version extends classes that are not extended by
* the sender's version. This may also occur if the serialization stream has
* been tampered; hence, readObjectNoData is useful for initializing
* deserialized objects properly despite a "hostile" or incomplete source
* stream.
*
* <p>Serializable classes that need to designate an alternative object to be
* used when writing an object to the stream should implement this
* special method with the exact signature:
*
* <PRE>
* ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
* </PRE><p>
*
* This writeReplace method is invoked by serialization if the method
* exists and it would be accessible from a method defined within the
* class of the object being serialized. Thus, the method can have private,
* protected and package-private access. Subclass access to this method
* follows java accessibility rules. <p>
*
* Classes that need to designate a replacement when an instance of it
* is read from the stream should implement this special method with the
* exact signature.
*
* <PRE>
* ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
* </PRE><p>
*
* This readResolve method follows the same invocation rules and
* accessibility rules as writeReplace.<p>
*
* The serialization runtime associates with each serializable class a version
* number, called a serialVersionUID, which is used during deserialization to
* verify that the sender and receiver of a serialized object have loaded
* classes for that object that are compatible with respect to serialization.
* If the receiver has loaded a class for the object that has a different
* serialVersionUID than that of the corresponding sender's class, then
* deserialization will result in an {@link InvalidClassException}. A
* serializable class can declare its own serialVersionUID explicitly by
* declaring a field named <code>"serialVersionUID"</code> that must be static,
* final, and of type <code>long</code>:
*
* <PRE>
* ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
* </PRE>
*
* 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 <em>strongly
* recommended</em> 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
* <code>InvalidClassException</code>s 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 <code>private</code> modifier where
* possible, since such declarations apply only to the immediately declaring
* class--serialVersionUID fields are not useful as inherited members. Array
* classes cannot declare an explicit serialVersionUID, so they always have
* the default computed value, but the requirement for matching
* serialVersionUID values is waived for array classes.
*
* @author unascribed
* @see java.io.ObjectOutputStream
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutput
* @see java.io.ObjectInput
* @see java.io.Externalizable
* @since JDK1.1
*/
public interface Serializable {
}
接口本身未實現(xiàn)任何方法,但其注釋值得好好看一下(只翻譯部分,最好自己看原文吧):
一個類的序列化能力是由實現(xiàn)Serializable接口決定的。未實現(xiàn)該接口的類將無法實現(xiàn)序列化和反序列化,實現(xiàn)序列化的類的子類也可以實現(xiàn)序列化。Serializable接口沒有任何方法和屬性,只是一個類可以實現(xiàn)序列化的標(biāo)志。
子類實現(xiàn)序列化,父類不實現(xiàn)序列化,此時父類要實現(xiàn)一個無參數(shù)構(gòu)造器,否則會報錯(見坑二)
遇到不支持序列化的類會拋出NotSerializableException
在序列化的過程中需要特殊處理時,可以通過實現(xiàn)writeObject,readObject,readObjectNoData來實現(xiàn)writeObject實現(xiàn)序列化將屬性和值寫入,默認(rèn)的寫入機(jī)制由defaultWriteObject來實現(xiàn)
readObject實現(xiàn)從數(shù)據(jù)流中重建對像,默認(rèn)的讀出機(jī)制由defaultReadObject來實現(xiàn),(This handles the case when the class has evolved to add new fields)而且可以處理類演化(添加字段)的情況,那刪除一個字段呢?見坑三.)如果某個超類不支持序列化,但又不希望使用默認(rèn)值怎么辦?實現(xiàn)readObjectNoData
writeReplace() 方法可以使對象被寫入流以前,用一個對象來替換自己。當(dāng)序列化時,可序列化的類要將對象寫入流,如果我們想要另一個對象來替換當(dāng)前對象來寫入流,則可以要實現(xiàn)下面這個方法,方法的簽名也要完全一致:readResolve (常用于單例模式)方法在對象從流中讀取出來的時候調(diào)用, ObjectInputStream 會檢查反序列化的對象是否已經(jīng)定義了這個方法,如果定義了,則讀出來的對象返回一個替代對象。同 writeReplace()方法,返回的對象也必須是與它替換的對象兼容,否則拋出 ClassCastException
serialVersionUID 相關(guān)見下面的(兼容性)
2.3 ObjectOutputStream
/**
* Write the specified object to the ObjectOutputStream. The class of the
* object, the signature of the class, and the values of the non-transient
* and non-static fields of the class and all of its supertypes are
* written. Default serialization for a class can be overridden using the
* writeObject and the readObject methods. Objects referenced by this
* object are written transitively so that a complete equivalent graph of
* objects can be reconstructed by an ObjectInputStream.
*
* <p>Exceptions are thrown for problems with the OutputStream and for
* classes that should not be serialized. All exceptions are fatal to the
* OutputStream, which is left in an indeterminate state, and it is up to
* the caller to ignore or recover the stream state.
*
* @throws InvalidClassException Something is wrong with a class used by
* serialization.
* @throws NotSerializableException Some object to be serialized does not
* implement the java.io.Serializable interface.
* @throws IOException Any exception thrown by the underlying
* OutputStream.
*/
public final void writeObject(Object obj) throws IOException {
//是否重寫了Object方法
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
// 寫入操作的具體實現(xiàn)
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
先來對上面的注釋翻譯一下:將一個具體的object寫入ObjectOutputStream.類名、類的簽名(可以理解為類名和UID,雖然不止這些),除non-transient和靜態(tài)屬性外屬于和值以及其超類??梢栽谧宇愔兄貙憌riteObject 和 readObject 方法,一個實例的多個引用,采用瞬態(tài)的寫入方式(坑1參考下面的介紹),因此可以構(gòu)造出一個完整的類的結(jié)構(gòu)圖。
writeObject0具體實現(xiàn)一個類的寫入,源碼如下(只保留了關(guān)鍵部分):
折疊原碼
/**
* Underlying writeObject/writeUnshared implementation.
*/
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
....
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
.....
}
可以看出,支持寫入的有幾種類型,包括String,Array,Enum,和Serializable(這就是實現(xiàn)Serializable的目的),當(dāng)然原生類型也會以數(shù)據(jù)塊的形式寫入(其實最終寫入的肯定是原生類型)。
對于Enum類型有必要單獨說一下(見坑四)。
此時我們可能會想知道,到底寫了哪些值(writeOrdinaryObject)
/**
* Writes representation of a "ordinary" (i.e., not a String, Class,
* ObjectStreamClass, array, or enum constant) serializable object to the
* stream.
*/
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
desc.checkSerialize();
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
到此為止,我們知道寫入了一個二進(jìn)制數(shù)據(jù)塊,其中包含類名、簽名、屬性名、屬性類型、及屬性值,當(dāng)然還有開頭結(jié)尾等數(shù)據(jù)。我們將二進(jìn)制轉(zhuǎn)換為UTF-8后如下
?í^@^Esr^@)com.sankuai.meituan.meishi.poi.tag.PersonY<9f>;<9d><8e>^B°3^B^@^BI^@^CageL^@^Dnamet^@^RLjava/lang/String;xp^@^@^@t^@^Hxiaoming
2.4 ObjectInputStream
理解ObjectOutputStream再來理解ObjectInputStream就簡單很多了,大概過一下
/**
* Read an object from the ObjectInputStream. The class of the object, the
* signature of the class, and the values of the non-transient and
* non-static fields of the class and all of its supertypes are read.
* Default deserializing for a class can be overriden using the writeObject
* and readObject methods. Objects referenced by this object are read
* transitively so that a complete equivalent graph of objects is
* reconstructed by readObject.
*
* <p>The root object is completely restored when all of its fields and the
* objects it references are completely restored. At this point the object
* validation callbacks are executed in order based on their registered
* priorities. The callbacks are registered by objects (in the readObject
* special methods) as they are individually restored.
*
* <p>Exceptions are thrown for problems with the InputStream and for
* classes that should not be deserialized. All exceptions are fatal to
* the InputStream and leave it in an indeterminate state; it is up to the
* caller to ignore or recover the stream state.
*
* @throws ClassNotFoundException Class of a serialized object cannot be
* found.
* @throws InvalidClassException Something is wrong with a class used by
* serialization.
* @throws StreamCorruptedException Control information in the
* stream is inconsistent.
* @throws OptionalDataException Primitive data was found in the
* stream instead of objects.
* @throws IOException Any of the usual Input/Output related exceptions.
*/
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
還是看一下注釋:讀關(guān)類(類名), 簽名、非瞬態(tài)非靜態(tài)屬性值和屬性名。
剩下的注解也和ObjectOutputStream基本一致
實際解析的數(shù)據(jù)是readObject0
readObject0就是按照協(xié)議進(jìn)行解析數(shù)據(jù)了
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
}
depth++;
try {
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
return readHandle(unshared);
case TC_CLASS:
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
return checkResolve(readArray(unshared));
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
三、兼容性
java序列化是通過在運(yùn)行時判斷serialVersionUID來驗證版本的一致性。在進(jìn)行反序列化時,JVM會把傳過來的字節(jié)流中serialVersionUID與本地相應(yīng)的實體(類)的serialVersionUID進(jìn)行對比, 如果相同則是認(rèn)為一致的,否則就會拋出異常InvalidClassException。
serialVersionUID有兩種生成方式:默認(rèn)生成和顯示指定。具體實現(xiàn)方式如下:
/**
* Adds serialVersionUID if one does not already exist. Call this before
* modifying a class to maintain serialization compatability.
*/
public static void setSerialVersionUID(CtClass clazz)
throws CannotCompileException, NotFoundException
{
// check for pre-existing field.
try {
clazz.getDeclaredField("serialVersionUID");
return;
}
catch (NotFoundException e) {}
// check if the class is serializable.
if (!isSerializable(clazz))
return;
// add field with default value.
CtField field = new CtField(CtClass.longType, "serialVersionUID",
clazz);
field.setModifiers(Modifier.PRIVATE | Modifier.STATIC |
Modifier.FINAL);
clazz.addField(field, calculateDefault(clazz) + "L");
}
默認(rèn)生成的UID的值計算方式參考如下源碼:
可以看出UID的值來源于類的幾個方面:類名(class name)、類及其屬性的修飾符(class modifiers)、 接口及接口順序(interfaces)、屬性(fields)、靜態(tài)初始化(static initializer), 構(gòu)造器(constructors)。也就是說這其中任何一個的改變都會影響UID的值,導(dǎo)致不兼容性。
/**
* Calculate default value. See Java Serialization Specification, Stream
* Unique Identifiers.
*/
static long calculateDefault(CtClass clazz)
throws CannotCompileException
{
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bout);
ClassFile classFile = clazz.getClassFile();
// class name.
String javaName = javaName(clazz);
out.writeUTF(javaName);
CtMethod[] methods = clazz.getDeclaredMethods();
// class modifiers.
int classMods = clazz.getModifiers();
if ((classMods & Modifier.INTERFACE) != 0)
if (methods.length > 0)
classMods = classMods | Modifier.ABSTRACT;
else
classMods = classMods & ~Modifier.ABSTRACT;
out.writeInt(classMods);
// interfaces.
String[] interfaces = classFile.getInterfaces();
for (int i = 0; i < interfaces.length; i++)
interfaces[i] = javaName(interfaces[i]);
Arrays.sort(interfaces);
for (int i = 0; i < interfaces.length; i++)
out.writeUTF(interfaces[i]);
// fields.
CtField[] fields = clazz.getDeclaredFields();
Arrays.sort(fields, new Comparator() {
public int compare(Object o1, Object o2) {
CtField field1 = (CtField)o1;
CtField field2 = (CtField)o2;
return field1.getName().compareTo(field2.getName());
}
});
for (int i = 0; i < fields.length; i++) {
CtField field = (CtField) fields[i];
int mods = field.getModifiers();
if (((mods & Modifier.PRIVATE) == 0) ||
((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0)) {
out.writeUTF(field.getName());
out.writeInt(mods);
out.writeUTF(field.getFieldInfo2().getDescriptor());
}
}
// static initializer.
if (classFile.getStaticInitializer() != null) {
out.writeUTF("<clinit>");
out.writeInt(Modifier.STATIC);
out.writeUTF("()V");
}
// constructors.
CtConstructor[] constructors = clazz.getDeclaredConstructors();
Arrays.sort(constructors, new Comparator() {
public int compare(Object o1, Object o2) {
CtConstructor c1 = (CtConstructor)o1;
CtConstructor c2 = (CtConstructor)o2;
return c1.getMethodInfo2().getDescriptor().compareTo(
c2.getMethodInfo2().getDescriptor());
}
});
for (int i = 0; i < constructors.length; i++) {
CtConstructor constructor = constructors[i];
int mods = constructor.getModifiers();
if ((mods & Modifier.PRIVATE) == 0) {
out.writeUTF("<init>");
out.writeInt(mods);
out.writeUTF(constructor.getMethodInfo2()
.getDescriptor().replace('/', '.'));
}
}
// methods.
Arrays.sort(methods, new Comparator() {
public int compare(Object o1, Object o2) {
CtMethod m1 = (CtMethod)o1;
CtMethod m2 = (CtMethod)o2;
int value = m1.getName().compareTo(m2.getName());
if (value == 0)
value = m1.getMethodInfo2().getDescriptor()
.compareTo(m2.getMethodInfo2().getDescriptor());
return value;
}
});
for (int i = 0; i < methods.length; i++) {
CtMethod method = methods[i];
int mods = method.getModifiers()
& (Modifier.PUBLIC | Modifier.PRIVATE
| Modifier.PROTECTED | Modifier.STATIC
| Modifier.FINAL | Modifier.SYNCHRONIZED
| Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT);
if ((mods & Modifier.PRIVATE) == 0) {
out.writeUTF(method.getName());
out.writeInt(mods);
out.writeUTF(method.getMethodInfo2()
.getDescriptor().replace('/', '.'));
}
}
// calculate hash.
out.flush();
MessageDigest digest = MessageDigest.getInstance("SHA");
byte[] digested = digest.digest(bout.toByteArray());
long hash = 0;
for (int i = Math.min(digested.length, 8) - 1; i >= 0; i--)
hash = (hash << 8) | (digested[i] & 0xFF);
return hash;
}
catch (IOException e) {
throw new CannotCompileException(e);
}
catch (NoSuchAlgorithmException e) {
throw new CannotCompileException(e);
}
}
顯示指定:
private static final long serialVersionUID = 1L;
那兩種方式使用的情景是什么呢?我認(rèn)為應(yīng)該把握一個判斷原則:是否允許向下兼容。
默認(rèn)方式使用情景:一旦創(chuàng)建則不允許改變
顯示方式使用情景:對類有一定的向下兼容性(稍后將具體分析哪些情況兼容),當(dāng)不允許兼容時,可以通過改變UID的值在實現(xiàn)。
強(qiáng)烈建議使用顯示指定的方式,以防范潛在的不兼容根源,且可以帶來小小的性能提升。
四、坑
(下面的坑都是在指定顯示指定UID并且一致的情況下產(chǎn)生的,非顯示指定UID的坑更多,不再介紹了)
4.1 坑1(多引用寫入)
@Test
public void testSerializable() throws Exception {
File file = new File("p.dat");
Person p = new Person("xiaoming", 10);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(p);
p.setAge(20);
oos.writeObject(p);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Person p1 = (Person) ois.readObject();
Person p2 = (Person) ois.readObject();
ois.close();
System.out.println(p1.toString() + "name:"+p1.getName() + "age:"+p1.getAge());
System.out.println(p2.toString() + "name:"+p2.getName() + "age:"+p2.getAge());
}
讀出來的結(jié)果
com.sankuai.meituan.meishi.poi.tag.Person@b7f23d9name:xiaomingage:10
com.sankuai.meituan.meishi.poi.tag.Person@b7f23d9name:xiaomingage:10
是不是和希望的不一樣?其實在默認(rèn)情況下,對于一個實例的多個引用,為了節(jié)省空間,只會寫入一次,后面會追加幾個字節(jié)代表某個實例的引用。
我們可能通過rest或writeUnshared方法對一個實例多次寫入,如下:
@Test
public void testSerializable() throws Exception {
File file = new File("p.dat");
Person p = new Person("xiaoming", 10);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(p);
p.setAge(20);
oos.reset();
//oos.writeUnshared(p);
oos.writeObject(p);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Person p1 = (Person) ois.readObject();
Person p2 = (Person) ois.readObject();
ois.close();
System.out.println(p1.toString() + "name:"+p1.getName() + "age:"+p1.getAge());
System.out.println(p2.toString() + "name:"+p2.getName() + "age:"+p2.getAge());
System.out.println(p2.toString() + "name:"+p2.getName() + "age:"+p2.getAge());
}
結(jié)果如下:
com.sankuai.meituan.meishi.poi.tag.Person@b7f23d9name:xiaomingage:10
com.sankuai.meituan.meishi.poi.tag.Person@61d47554name:xiaomingage:20
4.2 坑2(子父引用序列化)
子類實現(xiàn)序列化,父類不實現(xiàn)序列化
父類是Person,定義一個字類Student
public class Student extends Person implements Serializable {
private static final long serialVersionUID = 1L;
private int studentId;
public Student(String name, int age, int studentId) {
super(name,age);
this.studentId = studentId;
}
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
}
測試代碼如下:
@Test
public void testSerializable() throws Exception {
File file = new File("p.dat");
Student s = new Student( "xiaoming", 10, 1 );
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(s);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Student s1 = (Student) ois.readObject();
ois.close();
System.out.println(s1.toString() + "name:"+s1.getName() + "age:"+s1.getAge() + "height:"+s1.getStudentId());
System.out.println(s1.toString() + "name:"+s1.getName() + "age:"+s1.getAge() + "height:"+s1.getStudentId());
}
在readObject時拋出java.io.NotSerializableException異常。
我們更改一下Person,添加一個無參數(shù)構(gòu)造器
public class Student extends Person implements Serializable {
private static final long serialVersionUID = 1L;
private int studentId;
public Student(String name, int age, int studentId) {
super(name,age);
this.studentId = studentId;
}
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
}
結(jié)果如下
com.sankuai.meituan.meishi.poi.tag.Student@12405818name:nullage:0height:1
這是因為當(dāng)父類不可序列化時,需要調(diào)用默認(rèn)無參構(gòu)造器初始化屬性的值。
對象引用
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private int studentId;
private Person person;
public Student(int studentId, Person person) {
this.studentId = studentId;
this.person = person;
}
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
@Test
public void testSerializable() throws Exception {
File file = new File("p.dat");
Student s = new Student( 1 , new Person("xiaoming", 10));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(s);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Student s1 = (Student) ois.readObject();
ois.close();
System.out.println(s1.toString() + "name:"+s1.getPerson().getName() + "age:"+s1.getPerson().getAge() + "height:"+s1.getStudentId());
}
仍然模擬兩種情況(實現(xiàn)無參構(gòu)造器和不實現(xiàn)無參數(shù)構(gòu)造器),
發(fā)現(xiàn)兩種情況都會拋出java.io.NotSerializableException異常,這就需要可序列化類的每個屬性都要可序列化(當(dāng)然去瞬態(tài)屬性和靜態(tài)屬性).
4.3 坑三(類的演化)
演化類如下:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private int height;
public Person(String name, int age, int height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
反序列化目標(biāo)類多一個字段(height),序列化寫入的Person 包含兩個屬性:name,age
@Test
public void testSerializable() throws Exception {
File file = new File("p.dat");
/*Person p = new Person("xiaoming", 10);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(p);*/
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Person p1 = (Person) ois.readObject();
ois.close();
System.out.println(p1.toString() + "name:"+p1.getName() + "age:"+p1.getAge() + "height:"+p1.getHeight());
}
結(jié)果如下
com.sankuai.meituan.meishi.poi.tag.Person@37574691name:xiaomingage:10height:0
可以看出反序列化之后,并沒有報錯,只是height實賦成了默認(rèn)值。類似的其它對象也會賦值為默認(rèn)值。
相反,如果寫入的多一個字段,讀出的少一個字段
com.sankuai.meituan.meishi.poi.tag.Person@37574691name:xiaomingage:10
其它演化,比如更改類型等,這種演化本身就有問題,沒必要再探討。
4.4 坑四(枚舉類型)
對于枚舉類型,我們經(jīng)常會調(diào)整對象的值,我們這里使用默認(rèn)值(0,1,2)進(jìn)行序列化,然后調(diào)整元素順序進(jìn)行反序列化,看看會發(fā)生什么現(xiàn)象(是0,1,2還是2,1,0);
枚舉類
public enum Num {
ONE,TWO,THREE;
public void printValues() {
System.out.println(ONE + "ONE.ordinal" + ONE.ordinal());
System.out.println(TWO + "TWO.ordinal" + TWO.ordinal());
System.out.println(THREE + "THREE.ordinal" + THREE.ordinal());
}
}
序列化
@Test
public void testSerializable() throws Exception {
File file = new File("p.dat");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(Num.ONE);
oos.close();
/* ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Num s1 = (Num) ois.readObject();
s1.printValues();
ois.close();
*/
}
我們只寫入一個ONE值,
?í^@^E~r^@&com.sankuai.meituan.meishi.poi.tag.Num^@^@^@^@^@^@^@^@^R^@^@xr^@^Njava.lang.Enum^@^@^@^@^@^@^@^@^R^@^@xpt^@^CONE
對其調(diào)整順序(THREE,TWO,ONE;)再讀出文件中讀出結(jié)果,看看會是什么現(xiàn)象
NEONE.ordinal2
TWOTWO.ordinal1
THREETHREE.ordinal0
可以看到ONE的值變成了2.
事實上序列化Enum對象時,并不會保存元素的值,只會保存元素的name。這樣,在不依賴元素值的前提下,ENUM對象如何更改都會保持兼容性。
五、重寫readObject,writeObject
怎么樣重寫這里就不說了,在這里引用effective java的一句話告訴你什么時候重寫:
“只有當(dāng)你自行設(shè)計的自定義序列化形式與默認(rèn)的序列化形式基本相同時,才能接受默認(rèn)的序列化形式”.
“當(dāng)一個對象的物理表示方法與它的邏輯數(shù)據(jù)內(nèi)容有實質(zhì)性差別時,使用默認(rèn)序列化形式有N種缺陷”.
其實從effective java的角度來講,是強(qiáng)烈建議我們重寫的,這樣有助于我們更好地把控序列化過程,防范未知風(fēng)險