Java 序列化

来源:互联网 发布:索尼手机录音软件 编辑:程序博客网 时间:2024/06/07 16:59


1. 基本的对象序列化

Serializable,ObjectOutputStream, ObjectInputStream

在写入和读取的时候,虽然用的参数或返回值是单个对象,但实际上操纵的是一个对象图,包括该对象所引用的其它对象,以 及这些对象所引用的另外的对象

在默认的序列化实现中,Java对象中的非静态和非瞬时域(transient)都会被包括进来,而与域的可见性声明没有关系

自定义需要序列化的域的两种方式:

1. 用关键字 transient 表示某个域不需要序列化

2. 添加一个serialPersistentFields 域来声明序列化时要包含的域 (此静态变量的名称是固定的,属于隐含的契约)。注意此变量问privatestatic final

private static finalObjectStreamField[] serialPersistentFields = {

        newObjectStreamField("firstName", String.class)

};

 

2. 自定义对象序列化

基本的对象序列化机制让开发人员可以在包含哪些域上进行定制。如果想对序列化的过程进行更加细粒度的控制,就需要在类中添加writeObject 和对应的 readObject方法。这两个方法属于前面提到的序列化机制的隐含契约的一部分

在添加自己的逻辑之前,推荐的做法是先调用Java的默认实现。在writeObject方法中通过ObjectOutputStream的defaultWriteObject来完成,在readObject方法则通过
ObjectInputStream的defaultReadObject来实现

private voidwriteObject(ObjectOutputStream output) throws IOException

{

        output.defaultWriteObject();

        output.writeUTF("HelloWorld");

}

private voidreadObject(ObjectInputStream input) throws IOException, ClassNotFoundException{

        input.defaultReadObject();

        Stringvalue = input.readUTF();

        System.out.println(value);

}

 

3. 序列化与对象创建

在通过ObjectInputStream 的readObject 方法读取到一个对象之后,这个对象是一个新的实例,但是其构造方法是没有被调用的,其中的域的初始化代码也没有被执行。
对于那些没有被序列化的域,在新创建出来的对象中的值都是默认的。也就是说,这个对象从某种角度上来说是不完备的。这有可能会造成一些隐含的错误。调用者并不知道对象是通过一般的new 操作符来创建的,还是通过反序列化所得到的。解决的办法就是在类的readObject 方法里面,再执行所需的对象初始化逻辑。对于一般的Java 类来说,构造方法中包含了初始化的逻辑。可以把这些逻辑提取到一个方法中,在readObject 方法中调用此方法。

 

4. 版本更新

把一个Java对象序列化之后,所得到的字节数组一般会保存在磁盘或数据库之中。在保存完成之后,有可能原来的Java类有了更新,比如添加了额外的域。这个时候从兼容性的角度出发,要求仍然能够读取旧版本的序列化数据。在读取的过程中,当ObjectInputStream发现一个对象的定义的时候,会尝试在当前JVM中查找其Java类定义。这个查找过程不能仅根据Java类的全名来判断,因为当前JVM中可能存在名称相同,但是含义完全不同的 Java 类。这个对应关系是通过一个全局惟一标识符serialVersionUID来实现的。通过在实现了Serializable接口的类中定义该域,就声明了该Java类的一个惟一的序列化版本号。JVM会比对从字节数组中得出的类的版本号,与JVM中查找到的类的版本号是否一致,来决定两个类是否是兼容的。对于开发人员来说,需要记得的就是在实现了Serializable接口的类中定义这样的一个域,并在版本更新过程中保持该值不变。当然,如果不希望 维持这种向后兼容性,换一个版本号即可。该域的值一般是综合Java类的各个特性而计算出来的一个哈希值,可以通过Java提供的serialver命令来生成。在Eclipse中,如果Java类实现了Serializable接口,Eclipse会提示并帮你生成这个serialVersionUID

如果没有声明serialVersionUID, JVM runtime会在运行时根据类内部的值计算出serialVersionUID。

 

5. 序列化安全性

前面提到,Java对象序列化之后的内容格式是公开的。所以可以很容易的从中提取出各种信息。从实现的角度来说,可以从不同的层次来加强序列化的安全性。对序列化之后的流进行加密。这可以通过CipherOutputStream来实现。实现自己的writeObject 和readObject方法,在调用defaultWriteObject 之前,先对要序列化的域的值进行加密处理。使用一个SignedObject 或SealedObject来封装当前对象, 用SignedObject 或SealedObject进行序列化。在从流中进行反序列化的时候,可以通过ObjectInputStream的registerValidation方法添加ObjectInputValidation接口的实现,用来验证反序列化之后得到的对象是否合法。


原创粉丝点击