统一自定义序列化
上一篇《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用得比较多,性能以及各种语言兼容性都不错,有时间在把这个源码分析下。
相关推荐
java 序列化和反序列化的方法 Java 序列化和反序列化是 Java 语言中的一种机制,用于将对象转换为字节流,以便在网络上传输或存储。序列化是将对象转换为字节流的过程,而反序列化是将字节流转换回对象的过程。 在...
- Java允许使用 `writeObject()` 和 `readObject()` 方法来自定义序列化和反序列化的行为,这两个方法需要在类中声明为`private`,并由`java.io.Serializable` 接口的实现类提供。 7. **序列化安全性** - 序列化...
Java序列化是Java平台中的一种持久化机制,它允许对象的状态被转换成字节流,以便存储、网络传输或在不同时间点恢复。这个过程被称为序列化,而反向操作称为反序列化。序列化在许多场景下都非常有用,比如在分布式...
- Java序列化:Java序列化直接操作Java对象,无需额外定义数据结构,但可能导致序列化后的数据不易跨语言使用。 3. 可读性与可维护性: - Protocol Buffer:PB的.proto文件提供了清晰的结构定义,易于理解和维护...
Java反序列化是一种将已序列化的对象状态转换回对象的过程,它是Java平台中持久化数据的一种常见方式。在Java应用程序中,序列化用于保存对象的状态以便稍后恢复,或者在网络间传输对象。然而,这个过程也可能引入...
### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化的概念 序列化是指将程序中的对象转换为一系列字节流的过程,主要用于保存对象的状态或在网络之间传输对象。序列化的主要目的是为了能够持久化...
- **使用`writeObject()`和`readObject()`方法**:重写`writeObject()`和`readObject()`方法来自定义序列化和反序列化行为。在`writeObject()`方法中,使用`defaultWriteFields()`方法来跳过不想要的字段,而在`...
这对于实现持久化、远程方法调用(RMI)以及Enterprise JavaBeans(EJB)等高级功能至关重要。 首先,要使一个Java对象能够被序列化,该类必须实现`Serializable`接口。这个接口没有任何方法,仅仅是一个标记接口,...
在Java中,一个类如果要实现序列化,需要实现`Serializable`接口,这是一个标记接口,不包含任何方法。下面我们将详细讨论Java序列化的用途、注意事项以及如何实现。 1. **序列化用途**: - **持久化存储**:将...
### Java序列化原理与算法详解 #### 序言 在现代软件开发中,尤其是在网络通信和数据持久化领域,对象的序列化与反序列化扮演着至关重要的角色。...在未来的学习和工作中,掌握序列化的使用方法将是一项宝贵的技能。
在Java环境中,Protobuf-java库提供了编译器工具,可以将.proto文件编译为Java类,这些类包含序列化和反序列化的方法。Protobuf特别适用于跨平台的通信,如服务器与客户端之间的数据交换,或者作为数据库的存储格式...
### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化是什么? 序列化是指将程序中的对象转换为字节流的过程,从而方便存储或传输这些对象。通常,序列化用于将对象的状态(即其实例变量的值,而非...
Java对象的序列化和反序列化是Java编程中一项重要的技术,主要用于将对象的状态转换为字节流,以便存储或在网络上传输。这一过程对于理解Java的IO操作、持久化数据以及实现分布式通信等场景非常关键。 首先,我们来...
而在Java中,我们可以通过实现`Serializable`接口来使类支持序列化,或者使用`java.io.ObjectOutputStream`和`java.io.ObjectInputStream`进行对象的序列化和反序列化。 接下来,我们讨论反序列化。反序列化是序列...
android(包括java)序列化一个对象传给php去做处理,或是接到php的序列化的对象在java中做处理的工具jar包以及使用方法. 使用方法: byte[] b = null; b = PHPSerializer.serialize(一个对象);//将一个对象序列化后返回...
`useProtocolVersion`方法允许指定使用的序列化协议版本。这在处理不同版本之间的兼容性问题时很有帮助。 #### 三、对象输入类 **3.1 `ObjectInputStream`类** `ObjectInputStream`类负责从序列化流中读取对象。...
Java的序列化与反序列化是Java开发中的一项重要技术,它允许我们将对象的状态转换为字节流,以便存储或在网络上传输。`Serializable`接口是Java提供的一个标记接口,用于实现对象的序列化。当一个类实现了这个接口,...
开发者可以使用protobuf编译器将.proto文件编译成Java、C++、Python等语言的类,这些类提供了序列化和反序列化的方法。protobuf的优势在于它产生的代码执行速度快,序列化后的数据体积小,且具有良好的版本兼容性。 ...
Xson是一个Java对象序列化和反序列化程序。支持Java对象到字节数组的序列化,和从字节数组到Java对象的反序列化。 Maven: <groupId>com.github.xsonorg</groupId> <artifactId>xson-core <version>1.0.1 ...
### Java序列化与反序列化详解 #### 一、Java序列化概述 Java序列化(Serialization)是一项重要的功能,它可以将对象的状态转化为一系列字节,从而实现对象的持久化存储或在网络上传输。序列化机制使得Java对象...