`

java序列化用法以及理论(后记)

阅读更多

统一自定义序列化

 

上一篇《java序列化用法以及理论()》讲解了两种自定义序列化方式:

1、自定义writeObjectreadObject方法

2、实现Externalizable接口,并重写writeExternalreadExternal方法

 

其实对于自定义序列化还有一种方式实现自定义,就是创建ObjectOutputStream

ObjectInputStream的子类,并重写相应的方法即可:

创建ObjectOutputStream的子类,重写writeObjectOverride方法,实现自己的序列化逻辑。

创建ObjectInputStream的子类,重写readObjectOverride方法,实现自己的反序列化逻辑。

假如有一批业务序列化类序列化规则都类似,如果采用上一篇提到的两种序列化方法,势必要去修改每一个类。采用自定义ObjectOutputStreamObjectInputStream子类的方式,就可以实现统一的自定义序列化规则。

 

第三种自定义序列化方式,我暂且称之为:“统一自定义序列化”。

 

这种方式是通过继承实现,对于继承还有另一个对象替换功能。

继承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;
}
 

 

 

从这里可以看到对于序列化对象,可以自定义这些方法:writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve方法(这些方法都是命名模式,注意在定义方法时别写错)。其中writeObjectreadObject这两个方法我们在上一篇已经讲解。下面我们主要结合源码以及使用场景针对readObjectNoDatawriteReplacereadResolve这三个方法进行讲解。

 

readObjectNoData方法

 

这个方法主要用来保证通过继承扩容后对老版本的兼容性,适用场景如下:比如待类Persons,被序列化到硬盘后存储为文件old.txtPersons被修改继承自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的父类Objectc为新Persons类父类Animals,不相等所以创建一个hasDatefalseClassDataSlot,采用导致源码片段A中反射调用readObjectNoData方法被执行。如果字节流是新Persons类序列化的,这时候endc是相等的都是Animals,不会创建hasDatefalseClassDataSlotreadObjectNoData方法也不会被执行。

 

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用得比较多,性能以及各种语言兼容性都不错,有时间在把这个源码分析下。

 

 

1
0
分享到:
评论
2 楼 moon_walker 2017-06-04  
seavers 写道
readResolve还可以用于替换成对应的子类,这个时候需要成员变量信息

能否简单描述下应用场景,谢谢
1 楼 seavers 2017-06-04  
readResolve还可以用于替换成对应的子类,这个时候需要成员变量信息

相关推荐

    java序列化和反序列化的方法

    java 序列化和反序列化的方法 Java 序列化和反序列化是 Java 语言中的一种机制,用于将对象转换为字节流,以便在网络上传输或存储。序列化是将对象转换为字节流的过程,而反序列化是将字节流转换回对象的过程。 在...

    Java序列化

    - Java允许使用 `writeObject()` 和 `readObject()` 方法来自定义序列化和反序列化的行为,这两个方法需要在类中声明为`private`,并由`java.io.Serializable` 接口的实现类提供。 7. **序列化安全性** - 序列化...

    Java序列化_Java序列化结构_

    Java序列化是Java平台中的一种持久化机制,它允许对象的状态被转换成字节流,以便存储、网络传输或在不同时间点恢复。这个过程被称为序列化,而反向操作称为反序列化。序列化在许多场景下都非常有用,比如在分布式...

    Protocol Buffer序列化对比Java序列化.

    - Java序列化:Java序列化直接操作Java对象,无需额外定义数据结构,但可能导致序列化后的数据不易跨语言使用。 3. 可读性与可维护性: - Protocol Buffer:PB的.proto文件提供了清晰的结构定义,易于理解和维护...

    java反序列化工具

    Java反序列化是一种将已序列化的对象状态转换回对象的过程,它是Java平台中持久化数据的一种常见方式。在Java应用程序中,序列化用于保存对象的状态以便稍后恢复,或者在网络间传输对象。然而,这个过程也可能引入...

    java序列化(Serializable)的作用和反序列化.doc

    ### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化的概念 序列化是指将程序中的对象转换为一系列字节流的过程,主要用于保存对象的状态或在网络之间传输对象。序列化的主要目的是为了能够持久化...

    java 序列化时排除指定属性

    - **使用`writeObject()`和`readObject()`方法**:重写`writeObject()`和`readObject()`方法来自定义序列化和反序列化行为。在`writeObject()`方法中,使用`defaultWriteFields()`方法来跳过不想要的字段,而在`...

    java序列化全解

    这对于实现持久化、远程方法调用(RMI)以及Enterprise JavaBeans(EJB)等高级功能至关重要。 首先,要使一个Java对象能够被序列化,该类必须实现`Serializable`接口。这个接口没有任何方法,仅仅是一个标记接口,...

    java 序列化代码示例

    在Java中,一个类如果要实现序列化,需要实现`Serializable`接口,这是一个标记接口,不包含任何方法。下面我们将详细讨论Java序列化的用途、注意事项以及如何实现。 1. **序列化用途**: - **持久化存储**:将...

    java序列化原理与算法

    ### Java序列化原理与算法详解 #### 序言 在现代软件开发中,尤其是在网络通信和数据持久化领域,对象的序列化与反序列化扮演着至关重要的角色。...在未来的学习和工作中,掌握序列化的使用方法将是一项宝贵的技能。

    Java序列化Jar包

    在Java环境中,Protobuf-java库提供了编译器工具,可以将.proto文件编译为Java类,这些类包含序列化和反序列化的方法。Protobuf特别适用于跨平台的通信,如服务器与客户端之间的数据交换,或者作为数据库的存储格式...

    java序列化(Serializable)的作用和反序列化

    ### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化是什么? 序列化是指将程序中的对象转换为字节流的过程,从而方便存储或传输这些对象。通常,序列化用于将对象的状态(即其实例变量的值,而非...

    java序列化实现演示

    Java序列化是Java平台中的一种标准机制,允许对象的状态被保存到磁盘或者在网络中进行传输,以便在后续的时间或地点恢复这些对象。这个过程包括两个主要操作:序列化(将对象转换为字节流)和反序列化(将字节流恢复...

    C#和Java的序列化反序列化

    而在Java中,我们可以通过实现`Serializable`接口来使类支持序列化,或者使用`java.io.ObjectOutputStream`和`java.io.ObjectInputStream`进行对象的序列化和反序列化。 接下来,我们讨论反序列化。反序列化是序列...

    java序列化对象传给php

    android(包括java)序列化一个对象传给php去做处理,或是接到php的序列化的对象在java中做处理的工具jar包以及使用方法. 使用方法: byte[] b = null; b = PHPSerializer.serialize(一个对象);//将一个对象序列化后返回...

    Java序列化的机制和原理

    Java的序列化机制允许开发者在类中定义`readObject()`和`writeObject()`方法来自定义序列化和反序列化的行为,或者使用`transient`关键字来排除某些字段不参与序列化。 总之,Java序列化是一个强大的工具,它使得...

    Java对象序列化标准最新版

    `useProtocolVersion`方法允许指定使用的序列化协议版本。这在处理不同版本之间的兼容性问题时很有帮助。 #### 三、对象输入类 **3.1 `ObjectInputStream`类** `ObjectInputStream`类负责从序列化流中读取对象。...

    java serializable 序列化与反序列化

    Java的序列化与反序列化是Java开发中的一项重要技术,它允许我们将对象的状态转换为字节流,以便存储或在网络上传输。`Serializable`接口是Java提供的一个标记接口,用于实现对象的序列化。当一个类实现了这个接口,...

    java序列化之protobuf

    开发者可以使用protobuf编译器将.proto文件编译成Java、C++、Python等语言的类,这些类提供了序列化和反序列化的方法。protobuf的优势在于它产生的代码执行速度快,序列化后的数据体积小,且具有良好的版本兼容性。 ...

    Java对象序列化和反序列化工具Xson.zip

    Xson是一个Java对象序列化和反序列化程序。支持Java对象到字节数组的序列化,和从字节数组到Java对象的反序列化。 Maven:  &lt;groupId&gt;com.github.xsonorg&lt;/groupId&gt;  &lt;artifactId&gt;xson-core  &lt;version&gt;1.0.1 ...

Global site tag (gtag.js) - Google Analytics