`

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还可以用于替换成对应的子类,这个时候需要成员变量信息

相关推荐

Global site tag (gtag.js) - Google Analytics