统一自定义序列化
上一篇《java序列化用法以及理论(三)》讲解了两种自定义序列化方式:
1、自定义writeObject和readObject方法
2、实现Externalizable接口,并重写writeExternal和readExternal方法
其实对于自定义序列化还有一种方式实现自定义,就是创建ObjectOutputStream
和ObjectInputStream的子类,并重写相应的方法即可:
创建ObjectOutputStream的子类,重写writeObjectOverride方法,实现自己的序列化逻辑。
创建ObjectInputStream的子类,重写readObjectOverride方法,实现自己的反序列化逻辑。
假如有一批业务序列化类序列化规则都类似,如果采用上一篇提到的两种序列化方法,势必要去修改每一个类。采用自定义ObjectOutputStream和ObjectInputStream子类的方式,就可以实现统一的自定义序列化规则。
第三种自定义序列化方式,我暂且称之为:“统一自定义序列化”。
这种方式是通过继承实现,对于继承还有另一个对象替换功能。
继承ObjectOutputStream的子类可以设置enableReplace=true开启替换,并重写replaceObject方法返回需要替换的对象。在进行序列化时,序列化的实际上是替换对象。具体用法可以在《java序列化的内部实现(一)》中找到。
特殊方法存在检查
在反序列化过程中初始化ObjectStreamClass时,进行检查,这里检查内容比较多,再回顾下:
private ObjectStreamClass(final Class<?> cl) { //**********省略代码********** if (externalizable) { cons = getExternalizableConstructor(cl); } else {//对于实现Serializable接口特有的检查 cons = getSerializableConstructor(cl); writeObjectMethod = getPrivateMethod(cl, "writeObject", new Class<?>[] { ObjectOutputStream.class }, Void.TYPE);//检查对象是定义writeObject方法 readObjectMethod = getPrivateMethod(cl, "readObject", new Class<?>[] { ObjectInputStream.class }, Void.TYPE); //检查对象是定义readObject方法 readObjectNoDataMethod = getPrivateMethod( cl, "readObjectNoData", null, Void.TYPE);//检查对象是否定义readObjectNoData方法 hasWriteObjectData = (writeObjectMethod != null); } writeReplaceMethod = getInheritableMethod( cl, "writeReplace", null, Object.class);//检查对象是否有writeReplace方法 readResolveMethod = getInheritableMethod( cl, "readResolve", null, Object.class);//检查对象是否有readResolve方法 return null; //**********省略代码********** initialized = true; }
从这里可以看到对于序列化对象,可以自定义这些方法:writeObject、readObject、readObjectNoData、writeReplace、readResolve方法(这些方法都是“命名模式”,注意在定义方法时别写错)。其中writeObject、readObject这两个方法我们在上一篇已经讲解。下面我们主要结合源码以及使用场景针对readObjectNoData、writeReplace、readResolve这三个方法进行讲解。
readObjectNoData方法
这个方法主要用来保证通过继承扩容后对老版本的兼容性,适用场景如下:比如待类Persons,被序列化到硬盘后存储为文件old.txt,Persons被修改继承自Animals。为了保证用新版Persons序列化老版本old.txt文件,并且Animals的成员有默认值,可以在Animals类中定义readObjectNoData方法,返回默认值。有点绕,看例子就明白了。
代码片段1,对老版本的Persons类对象进行序列化存储到D盘:
public class ReadObjectNoDataTest implements Serializable{ private static final long serialVersionUID = 1L; public static void main(String[] args) { Persons p = new Persons(); p.setAge(10); ObjectOutputStream oos; try { //先对旧的类对象进行序列化 oos = new ObjectOutputStream(new FileOutputStream("D://old.txt")); oos.writeObject(p); oos.flush(); oos.close(); } catch (Exception e) { e.printStackTrace(); } } } class Persons implements Serializable {// private static final long serialVersionUID = 1L; private int age; public Persons() { } public void setAge(int age){ this.age = age; } public int getAge(){ return this.age; } }
执行mian方法,D盘已生成序列化文件old.txt
代码片段2,修改Persons类继承Animals方法,并使用新的Persons序列化old.txt文件:
public class ReadObjectNoDataTest implements Serializable{ private static final long serialVersionUID = 1L; public static void main(String[] args) { Persons p = new Persons(); p.setAge(10); ObjectOutputStream oos; try { //用新的类来反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D://old.txt")); Persons sp = (Persons) ois.readObject(); System.out.println(sp); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class Persons extends Animals implements Serializable { private static final long serialVersionUID = 1L; private int age; public Persons() { } public void setAge(int age){ this.age = age; } public int getAge(){ return this.age; } public String toString(){ return "name:"+getName()+" ,age:"+getAge(); } } class Animals implements Serializable { private String name; public Animals() { } public void setName(String name){ this.name = name; } public String getName(){ return this.name; } }
执行main方法,打印信息如下:
name:null ,age:10
如果我们希望name字段有默认值,这个时候可以在Animals类中定义readObjectNoData方法,如下:
class Animals implements Serializable { private String name; public Animals() { } public void setName(String name){ this.name = name; } public String getName(){ return this.name; } private void readObjectNoData() { this.name = "zhang san"; } }
重写执行main方法,打印信息如下:
name:zhangsan ,age:10
需要注意的是,如果采用新Persons类进行序列化,再进行反序列化,这时候打印的信息是:
name:null ,age:10
说明readObjectNoData方法只是在兼容的老版本的时候才会被执行。
至于为什么?我们可以看下反序列化源码(ObjectInputStream),在对成员变量赋值的方法里,判断成员变量是否有值,如果没有值,再判断该成员是否有readObjectNoData方法,如果有就调用该方法进行赋值,源码片段A。
private void readSerialData(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 (slots[i].hasData) { //省略 有值的逻辑 } else {//没值逻辑 if (obj != null && slotDesc.hasReadObjectNoDataMethod() && handles.lookupException(passHandle) == null) { slotDesc.invokeReadObjectNoData(obj);//反射调用对象的readObjectNoData方法 } } } }
这里的是否有值,其实是判断新类的父类是否在 待序列化的字节流中定义,源码片段B(ObjectStreamClass类):
for (ObjectStreamClass d = this; d != null; d = d.superDesc) { //.......省略代码........... // add "no data" slot for any leftover unmatched classes for (Class<?> c = start; c != end; c = c.getSuperclass()) {//这里的end为字节流中老Persons的父类Object,c为新Persons类父类Animals,不相等所以创建一个hasDate为false的ClassDataSlot slots.add(new ClassDataSlot(ObjectStreamClass.lookup(c, true), false));//这里的false在上面代码中表示slots[i].hasData 为false } // order slots from superclass -> subclass Collections.reverse(slots); return slots.toArray(new ClassDataSlot[slots.size()]); }
这也说明为什么“readObjectNoData方法只是在兼容的老版本的时候才会被执行”,关键就是这里的end为字节流中老Persons的父类Object,c为新Persons类父类Animals,不相等所以创建一个hasDate为false的ClassDataSlot,采用导致源码片段A中反射调用readObjectNoData方法被执行。如果字节流是新Persons类序列化的,这时候end和c是相等的都是Animals,不会创建hasDate为false的ClassDataSlot,readObjectNoData方法也不会被执行。
writeReplace方法:
跟继承ObjectOutputStream并重写replaceObject方法做对象替换的作用相似。区别是writeReplace方法是在待序列化类中定义,直接使用ObjectOutputStream(无需新建它的子类)的默认序列化方法就可以实现对象替换。看个例子:
/** * Created by gantianxing on 2017/5/30. */ public class Tiger implements Serializable{ private static final long serialVersionUID = 1L; private String name; private String tooth; public Tiger(String name, String tooth){ this.name = name; this.tooth = tooth; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void setTooth(String tooth) { this.tooth = tooth; } public String getTooth() { return tooth; } public void bite(){ System.out.println("Tiger:"+name+" is biting."); } private Object writeReplace()throws ObjectStreamException{ Tiger rep = new Tiger("n2","t2"); return rep; } @Override public String toString(){ return "name:"+name+",tooth:"+tooth; } public static void main(String[] args) throws Exception{ ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D://tiger.txt")); Tiger t = new Tiger("n1","t1"); out.writeObject(t); ObjectInputStream in = new ObjectInputStream(new FileInputStream("D://tiger.txt")); System.out.println(in.readObject()); } }
执行main方法,最终打印的结果是:
name:n2,tooth:t2
说明Tiger t = new Tiger("n1","t1");这对象在序列化化时已经被替换,反序列化出来的是n2。
替换的源码,在ObjectOutputStream类的writeObject0方法里:
for (;;) { // REMIND: skip this check for strings/arrays? Class<?> repCl; desc = ObjectStreamClass.lookup(cl, true); if (!desc.hasWriteReplaceMethod() || //判断对象是否有writeReplace方法 (obj = desc.invokeWriteReplace(obj)) == null || //如果有,反射调用 (repCl = obj.getClass()) == cl) { break; } cl = repCl; //进行替换 }
readResolve方法
readResolve方法是在反序列化的时候进行对象替换,主要应用场景是在单例类里使用,防止由于序列化和反序列化产生新对象。看个实例:
/** * 单例类 * Created by gantianxing on 2017/6/1. */ public class Singleton implements Serializable{ private static final long serialVersionUID = 1L; public static final Singleton INSTENCE = new Singleton(); private Singleton(){} public static Singleton getInstence(){ return INSTENCE; } public static void main(String[] args) throws Exception{ //step 1 序列化 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D://Singleton.txt")); Singleton singleton = Singleton.getInstence(); out.writeObject(singleton); //step2 反序列化 ObjectInputStream in = new ObjectInputStream(new FileInputStream("D://Singleton.txt")); Object newobj = in.readObject(); System.out.println("是否是同一个实例:"+(singleton == newobj)); } }
执行main方法,打印信息为:是否是同一个实例:false
说明序列化创建了一个新的对象,破坏了Singleton类单例的原则。我们可以在Singleton中添加readResolve方法,解决这个问题,修改后的代码如下:
/** * 单例类 * Created by gantianxing on 2017/6/1. */ public class Singleton implements Serializable{ private static final long serialVersionUID = 1L; public static final Singleton INSTENCE = new Singleton(); private Singleton(){} public static Singleton getInstence(){ return INSTENCE; } private Object readResolve(){ return INSTENCE; } public static void main(String[] args) throws Exception{ //step 1 序列化 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D://Singleton.txt")); Singleton singleton = Singleton.getInstence(); out.writeObject(singleton); //step2 反序列化 ObjectInputStream in = new ObjectInputStream(new FileInputStream("D://Singleton.txt")); Object newobj = in.readObject(); System.out.println("是否是同一个实例:"+(singleton == newobj)); } }
重写执行main方法,打印信息如下:
是否是同一个实例:true
关于readResolve方法被执行的源码位置是在ObjectInputStream类:
private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false);//反序列化类描述信息 desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) {//反序列化读取数据 readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())//判断是否对象是否存在readResolve方法 { Object rep = desc.invokeReadResolve(obj);//反射调用对象的readResolve方法进行对象替换 if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { handles.setObject(passHandle, obj = rep); } } return obj; }
通过源码可以看到,readResolve方法是在类描述和成员变量数据读取完成之后,才执行的,感觉比较浪费资源,还不清楚放在整个对象反序列化完成之后才做替换的具体用意。有知道的大神,可以告知下。
其他序列化框架
Java自带的序列化框架在性能上不是很好,现在的RPC框架大多采用第三方序列化框架。个人感觉msgpack用得比较多,性能以及各种语言兼容性都不错,有时间在把这个源码分析下。