Java序列化技术

什么是序列化

序列化,可以简单地理解为轻量级的持久化(persistence),而且是针对对象实例(instance)的持久化。

整个序列化的过程就是:将对象信息转化为二进制数据序列,里面保存了对象的类型信息、引用类型信息、对象状态信息(我理解为成员变量的值)。而反序列化,就是根据流(stream)中读取的字节序列信息,重新包装一个对象,他的状态和序列化之前的对象一样。

序列化的整个过程由虚拟机实现,因此即使在不同的虚拟机上,仍然可以做到对象还原。因此,序列化主要用于1.RMI 2.JavaBean的状态保存。

示例代码——使用序列化存取对象

  • 对象必须实现Serializable接口,这是标记接口,没有任何方法,但是必须实现这个接口才能使用序列化机制
  • 通过ObjectOutputStreamwriteObject(...)写入对象
  • 通过ObjectInputStreamreadObject(...)读取对象

序列化工具类

/**
 * @author luzj
 * @description:
 * 1. 处理对象的序列化以及反序列化的工具
 * 2. T:待序列化对象的对象的类型
 * @date 2018/3/2
 */
public class SerialUtil<T> {

    private String path;

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public SerialUtil(String path) {
        this.path = path;
    }

    /**
     * 序列化对象
     * @param obj
     */
    public void serialObj(T obj) {
        FileOutputStream outputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {
            outputStream = new FileOutputStream(this.getPath());
            objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(obj);
            objectOutputStream.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println(obj);
            try {
                if (objectOutputStream != null) {
                    objectOutputStream.close();
                }
                if (outputStream != null)
                    outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 反序列化对象
     */
    public void deSerialObj() {
        FileInputStream in = null;
        ObjectInputStream objectInputStream = null;
        T obj = null;
        try {
            in = new FileInputStream(this.getPath());
            objectInputStream = new ObjectInputStream(in);
            obj = (T) objectInputStream.readObject();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if (objectInputStream != null)
                    objectInputStream.close();
                if (in != null)
                    in.close();
                if (obj != null)
                    System.out.println(obj);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

用于序列化的类

public class MySerial implements Serializable {
    private static final long serialVersionUID = 1L;
    public String name;
    public int code;
    //transient变量不会参与序列化
    public transient int SNN;

    public MySerial(String name) {
        this.name = name;
    }

    public MySerial(String name, int code) {
        this.name = name;
        this.code = code;
        this.SNN = 40;
    }

    @Override
    public String toString() {
        return "name:" + name + "code:" + code + "SNN:" + SNN + ",hashcode:" + this.hashCode();
    }
}

测试

String path = "src/main/resource/myserial.ser";//本地存储地址
SerialUtil<MySerial> serialSerialUtil = new SerialUtil<>(path);
MySerial mySerial = new MySerial("ada wong", 233);
serialSerialUtil.serialObj(mySerial);
System.err.println("=======================================");
serialSerialUtil.deSerialObj();

输出结果

为什么会抛出ClassNotFoundException

我们知道,序列化很多时候会用于两个虚拟机之间传输对象。这个过程或许通过磁盘IO,或许通过网络,都没关系。

在反序列化的时候,虚拟机或重组序列化对象,此时如果本地虚拟机如果找不到对应类,就会报出ClassNotFoundException,因此在调用readObject()时需要捕获该异常。

比如上面的Myserial类,本地要一个,远程主机也必须有一个。否则就会报异常。

serialVersionUID的作用

每一个序列化对象会有一个serialVersionUID,那么他的作用是什么?我们知道,序列化很多时候会用于RMI,本地一个类,远程也必须有一个同功能的类。

可是,如果两个类的serialVersionUID不一样,就会报出反序列化失败的提示信息。

因此,互相传输的两个虚拟机的类UID必须相同

继承关系中的序列化

想象这样一种场景,我们序列化的对象有一个父类,但是父类没有实现Serialization接口,这时候会出现什么情况呢?

答案是,子类从父类中继承的属性不会被序列化。

示例代码

// 父类,未序列化
public class Person{
    public String name;
    public int code;

    @Override
    public String toString() {
        return "name:"+name+",code:"+code+",hashcode:"+this.hashCode();
    }
}

//子类,序列化
public class Nancy extends Person implements Serializable {
    public int age;

    @Override
    public String toString() {
        return "name:"+name+",code:"+code+",age:"+age+",hashcode"+this.hashCode();
    }
}

//测试父子序列化Nancy
String path = "src/main/resource/nancy.ser";
SerialUtil<Nancy> serialUtil = new SerialUtil<>(path);

Nancy nancy = new Nancy();
nancy.age = 123;
nancy.name= "nancy";
nancy.code = 213;

serialUtil.serialObj(nancy);
System.err.println("============================================");
serialUtil.deSerialObj();

测试结果

很容易看到,父类的属性值并没有很好还原,只是被重新初始化了。

另外补充一下,如果一个属性被transient修饰,也不会参与序列化。

控制序列化域反序列化的过程

我刚喜欢称他为序列化的前处理。

首先,我们必须在序列化对象里面添加两个方法:

private void readObject(ObjectInputStream inputStream)
private void writeObject(ObjectOutputStream objectOutputStream)

严格记住这两个方法签名,必须按照格式写。让人困惑的是,这两个方法不是定义在接口里,还是私有方法,怎么调用呢?实际上他是根据反射搜索出方法,在调用。

这里对参考文档的示例代码做一些改进,使看起来更加的形象。

示例代码

    /**
     * 1.对序列化过程控制
     * 2.加密某个字段
     * @param objectOutputStream
     */
    private void writeObject(ObjectOutputStream objectOutputStream) {
        System.err.println("序列化:");
        ObjectOutputStream.PutField putField;
        try {
            putField = objectOutputStream.putFields();
            System.out.println("原密码是:" + this.password);
            password = XOREncrypt.xOREnc(password);
            putField.put("password", password);
            System.out.println("加密后的密码:" + password);
            objectOutputStream.writeFields();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 1.反序列化控制过程
     * 2.解密password
     * @param inputStream
     */
    private void readObject(ObjectInputStream inputStream) {
         try {
           // inputStream.defaultReadObject();
            System.err.println("反序列化:");
            ObjectInputStream.GetField getField;
            getField = inputStream.readFields();
            password = (String) getField.get("password", "");
            System.out.println("加密密码:" + password);
            password = XOREncrypt.xOREnc(password);
            System.out.println("解密密码:" + password);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
//测试
 String path = "src/main/resource/encrypt.ser";
String password = "ada wong is my wife";
SerialUtil<EncryptSerial> serialSerialUtil = new SerialUtil<>(path);
EncryptSerial encryptSerial = new EncryptSerial(password);

serialSerialUtil.serialObj(encryptSerial);
System.out.println("============================================");
serialSerialUtil.deSerialObj();
  • readObject(...)控制反序列化过程
  • writeObject(...)控制序列化过程
  • XOREncrypt是我个人写的加密工具类,读者完全可以实现一个自己的工具类
  • 这两个方法完全取代了原来的序列化处理机制,如果在执行自定义序列化处理方法前,想执行默认的序列化机制,可以加入一行代码inputStream.defaultReadObject()即可。

测试结果

代码详情

本文代码地址

参考文章

深入理解JAVA序列化

Java - Serialization

《java编程思想第四版》

posted @ 2018-03-04 18:07  小小怪医芙兰  阅读(433)  评论(0编辑  收藏  举报