`
sgl124764903
  • 浏览: 174726 次
  • 性别: Icon_minigender_1
  • 来自: 邯郸
社区版块
存档分类
最新评论

java序列化二(转)

阅读更多

2 控制序列化技术

2.1 使用readObject 和writeObject方法

由于我们对于对象的序列化是采用如下的类来实现具体的序列化过程:

java.io.ObjectOutputStream

而该类主要是通过其writeObject 方法来实现对象的序列化过程,改类同时也提供了一

种机制来实现用户自定义writeObject 的功能。方法就是在我们的需要序列化的类里实现一

如何正确的使用Java序列化技术 技术研究系列

个writeObject方法,这个方法在ObjectOutputStream序列化该对象的时候就会自动的回调它。

从而完成我们自定义的序列化功能。

同样的,反序列化的类也实现了同样的回调机制,我们通过扩展其readObject来实现自

定义的反序列化机制。

通过这种灵活的回调机制就解决了上面提出的序列化带来的问题,针对上面的Person

的问题,我们编写如下的readObject方法就可以彻底避免population计数不准确的问题:

private void readObject(ObjectInputStream ois)

throws IOException, ClassNotFoundException

{

ois.defaultReadObject();

synchronized (Person.class)

{

population++;

}

System.out.println("Adjusting population in readObject");

}

2.2 序列化过程的类版本控制

本节讨论以下问题:

u 在对象反序列化过程中如何寻找对象的类;

u 如果序列化和反序列化两边的类不是同一个版本,如何控制;

2.2.1 序列化类的寻找机制

在对象的反序列化过程中,是一定需要被反序列化的类能被ClassLoader 找到的,否则

在反序列化过程中就会抛出java.lang.ClassNotFoundException 异常。关于ClassLoader 如何

寻找类,这里就不多说了,可以参考我的另一篇讨论ClassLoader 的文章《在非管理环境下

如何实现热部署》。我们这里只是关心该序列化对象对应的类是被哪个ClassLoader 给Load

的。为此,我们修改上面的

/**

* 修改后的反序列化类

*/

import java.io.*;

public class ReadInstance

{

public void readPerson(String filename)

{

如何正确的使用Java序列化技术 技术研究系列

try{

FileInputStream fis = new FileInputStream(filename);

ObjectInputStream ois = new ObjectInputStream(fis);

Object o = ois.readObject();

System.out.println("read object " + o);

System.out.println(this.getClass().getClassLoader());

Person person = (Person)o;

System.out.println(person.getClass().getClassLoader());

}catch(java.io.IOException ie)

{

ie.printStackTrace();

}catch(ClassNotFoundException ce)

{

ce.printStackTrace();

}

}

public static void main(String [] args) throws Exception

{

if (args.length != 1)

{

System.out.println("usage: java ReadInstance filename");

System.exit(-1);

}

ReadInstance readInstance = new ReadInstance();

readInstance.readPerson(args[0]);

}

我们主要通过背景为黄色的两行代码查看其类加载器,运行结果如下:

由此可以看出,序列化类的类加载器正式其反序列化实现类的类加载器。这样的话我们

就可以通过使最新的Person 类的版本发布为只有该反序列化器的ClassLoader可见。而较旧

的版本则不为该ClassLoader 可见的方法来避免在反序列化过程中类的多重版本的问题。当

然,下面就类的版本问题我们还要做专门的探讨。

如何正确的使用Java序列化技术 技术研究系列

2.2.2 序列化类多重版本的控制

如果在反序列化的JVM 里出现了该类的不同时期的版本,那么反序列化机制是如何处

理的呢?

为了避免这种问题,Java的序列化机制提供了一种指纹技术,不同的类带有不同版本的

指纹信息,通过其指纹就可以辨别出当前JVM 里的类是不是和将要反序列化后的对象对应

的类是相同的版本。该指纹实现为一个64bit的long 类型。通过安全的Hash算法(SHA-1)

来将序列化的类的基本信息(包括类名称、类的编辑者、类的父接口及各种属性等信息)处

理为该64bit的指纹。我们可以通过JDK自带的命令serialver来打印某个可序列化类的指纹

信息。如下:

当我们的两边的类版本不一致的时候,反序列化就会报错:

如何正确的使用Java序列化技术 技术研究系列

解决之道:从上面的输出可以看出,该指纹是通过如下的内部变量来提供的:

private static final long serialVersionUID;

如果我们在类里提供对该属性的控制,就可以实现对类的序列化指纹的自定义控制。为

此,我们在Person 类里定义该变量:

private static final long serialVersionUID= 6921661392987334380L;

则当我们修改了Person 类,发布不同的版本到反序列化端的JVM,也不会有版本冲突

的问题了。需要注意的是,serialVersionUID 的值是需要通过serialver 命令来取得。而不能

自己随便设置,否则可能有重合的。

需要注意的是,手动设置serialVersionUID 有时候会带来一些问题,比如我们可能对类

做了关键性的更改。引起两边类的版本产生实质性的不兼容。为了避免这种失败,我们需要

知道什么样的更改会引起实质性的不兼容,下面的表格列出了会引起实质性不兼容和可以忽

略(兼容)的更改:

更改类型 例子

兼容的更改

u 添加属性(Adding fields)

u 添加/删除类(adding/removing classes)

u 添加/删除writeObject/readObject方法(adding/removing

writeObject/readObject)

u 添加序列化标志(adding Serializable)

u 改变访问修改者(changing access modifier)

u 删除静态/不可序列化属性(removing static/transient from

a field)

不兼容的更改

u 删除属性(Deleting fields)

u 在一个继承或者实现层次里删除类(removing classes in a

hierarchy)

u 添加静态/不可序列化字段(adding static/transient to a

field)

u 修改简单变量类型(changing type of a primitive)

u switching between Serializable or Externalizable

u 删除序列化标志(removing Serializable/Externalizable)

u 改变readObject/writeObject对默认属性值的控制(changing

whether readObject/writeObject handles default field

data)

u adding writeReplace or readResolve that produces

objects incompatible with older versions

另外,从Java 的序列化规范里并没有指出当我们对类做了实质性的不兼容修改后反序

列化会有什么后果。并不是所有的不兼容修改都会引起反序列化的失败。比如,如果我们删

除了一个属性,则在反序列化的时候,反序列化机制只是简单的将该属性的数据丢弃。从

JDK 的参考里,我们可以得到一些不兼容的修改引起的后果如下表:

如何正确的使用Java序列化技术 技术研究系列

不兼容的修改 引起的反序列化结果

删除属性

(Deleting a field) Silently ignored

在一个继承或者实现层次里删除类

(Moving classes in inheritance

hierarchy)

Exception

添加静态/不可序列化属性

(Adding static/transient)

Silently ignored

修改基本属性类型

(Changing primitive type)

Exception

改变对默认属性值的使用

(Changing use of default field data)

Exception

在序列化和非序列化及内外部类之间切换

(Switching Serializable and

Externalizable)

Exception

删除Serializable或者Externalizable标志

(Removing Serializable or

Externalizable)

Exception

返回不兼容的类

(Returning incompatible class)

Depends on incompatibility

2.3 显示的控制对属性的序列化过程

在默认的Java 序列化机制里,有关对象属性到byte 流里的属性的对应映射关系都是自

动而透明的完成的。在序列化的时候,对象的属性的名称默认作为byte 流里的名称。当该

对象反序列化的时候,就是根据byte 流里的名称来对应映射到新生成的对象的属性里去的。

举个例子来说。在我们的一个Person对象序列化的时候,Person的一个属性firstName就作

为byte 流里该属性默认的名称。当该Person 对象反序列化的时候,序列化机制就把从byte

流里得到的firstName 的值赋值给新的Person 实例里的名叫firstName的属性。

Java的序列化机制提供了相关的钩子函数给我们使用,通过这些钩子函数我们可以精确

的控制上述的序列化及反序列化过程。ObjectInputStream的内部类GetField提供了对把属性

数据从流中取出来的控制,而ObjectOutputStream的内部类PutField则提供了把属性数据放

入流中的控制机制。就ObjectInputStream来讲,我们需要在readObject方法里来完成从流中

读取相应的属性数据。比如我们现在把Person 类的版本从下面的表一更新到表二:

/**

* 修改前的老版本Person类,为了简化,我们删除了所有无关的代码

*/

import java.io.*;

public class Person extends Humanoid

implements java.io.Serializable

{

private String lastName;

如何正确的使用Java序列化技术 技术研究系列

private String firstName;

private static final long serialVersionUID =6921661392987334380L;

private Person()

{

}

public Person(String lastName, String firstName)

{

this.lastName = lastName;

this.firstName = firstName;

}

public String toString()

{

return "Person " + firstName + " " + lastName;

}

}

修改后的Person为:

/**

* 修改后的Person类,我们将firstName和lastName变成了fullName

*/

import java.io.*;

public class Person extends Humanoid

implements java.io.Serializable

{

private String fullName;

private static final long serialVersionUID =6921661392987334380L;

private Person()

{

}

public Person(String fullName)

{

this.lastName = fullName;

}

public String toString()

{

return "Person " + fullName;

}

}

为此,我们需要编写Person类的readObject方法如下:

private void readObject(ObjectInputStream ois)

throws IOException,ClassNotFoundException

{

ObjectInputStream.GetField gf = ois.readFields();

fullName = (String) gf.get("fullName", null);

if (fullName == null)

{

String lastName = (String) gf.get("lastName", null);

如何正确的使用Java序列化技术 技术研究系列

String firstName = (String) gf.get("firstName", null);

if ( (lastName == null) || (firstName == null))

{

throw new InvalidClassException("invalid Person");

}

fullName = firstName + " " + lastName;

}

}

我们的执行顺序是:

1) 编译老的Person及所有类;

2) 将老的Person序列化到文件里;

3) 修改为新版本的Person类;

4) 编译新的Person类;

5) 反序列化Person;

执行结果非常顺利,修改后的反序列化机制仍然正确的从流中获取了旧版本Person 的

属性信息并完成对新版本的Person的属性赋值。

使用ObjectInputStream的readObject 来处理反序列化的属性时,有两点需要注意:

u 一旦采用自己控制属性的反序列化,则必须完成所有属性的反序列化(即要给所有

属性赋值);

u 在使用内部类GetField 的get 方法的时候需要注意,如果get 的是一个既不在老版

本出现的属性,有没有在新版本出现的属性,则该方法会抛出异常:

IllegalArgumentException: no such field,所以我们应该在一个try块里

来使用该方法。

同理,我们可以通过writeObject 方法来控制对象属性的序列化过程。这里就不再一一

举例了,如果你有兴趣的话,可以自己实现Person 类的writeObject 方法,并且使用

ObjectOutputStream的内部类PutField来完成属性的手动序列化操作。

3 总结

Java 序列化机制提供了强大的处理能力。一般来讲,为了尽量利用Java 提供的自动化

机制,我们不需要对序列化的过程做任何的干扰。但是在某些时候我们需要实现一些特殊的

功能,比如类的多版本的控制,特殊字段的序列化控制等。我们可以通过多种方式来实现这

些功能:

u 利用序列化机制提供的钩子函数readObject和writeObject;

u 覆盖序列化类的metaData 信息;

如何正确的使用Java序列化技术 技术研究系列

u 使类实现Externalizable 接口而不是实现Serializable接口。

分享到:
评论

相关推荐

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

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

    Java序列化_Java序列化结构_

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

    Java序列化

    Java序列化是Java平台中的一种标准机制,允许将对象的状态转换为字节流,以便存储在磁盘上、通过网络进行传输或者在某些时候恢复原来的对象状态。这一过程包括两个主要步骤:对象的序列化(将对象转换为字节流)和反...

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

    【Protocol Buffer序列化对比Java序列化】 Protocol Buffer(简称PB)是Google开发的一种高效的数据序列化协议,而Java序列化是Java平台内置的一种序列化机制。两者的主要目标都是将对象转化为字节数组,便于在网络...

    java反序列化工具

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

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

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

    java 序列化时排除指定属性

    Java序列化是Java平台提供的一种持久化机制,它允许我们将一个Java对象转换为字节流,以便存储到磁盘上,或者通过网络进行传输。这使得我们可以保存和恢复对象的状态。实现序列化的类需要实现`Serializable`接口,...

    java序列化全解

    Java序列化是Java平台中的一种核心机制,它允许对象的状态被转换成字节流,以便存储到磁盘、数据库,或者在网络中进行传输。这对于实现持久化、远程方法调用(RMI)以及Enterprise JavaBeans(EJB)等高级功能至关...

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

    在C#中,我们可以使用.NET框架提供的`System.Runtime.Serialization.Formatters.Binary.BinaryFormatter`类来进行二进制序列化,或者使用`System.Xml.Serialization.XmlSerializer`来进行XML序列化。而在Java中,...

    java 序列化代码示例

    Java序列化是Java平台中的一种标准机制,它允许将对象的状态转换为字节流,以便存储、传输或恢复。在Java中,一个类如果要实现序列化,需要实现`Serializable`接口,这是一个标记接口,不包含任何方法。下面我们将...

    Java序列化Jar包

    Java序列化是Java平台中的一项重要技术,它允许对象的状态被转换为字节流,以便存储或通过网络进行传输。这种技术在分布式系统、持久化存储以及数据交换等场景中非常常见。本资源包含了三个流行的Java序列化框架:...

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

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

    java序列化原理与算法

    ### Java序列化原理与算法详解 #### 序言 在现代软件开发中,尤其是在网络通信和数据持久化领域,对象的序列化与反序列化扮演着至关重要的角色。Java作为一种广泛应用的编程语言,提供了强大的内置支持来实现序列化...

    java序列化实现演示

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

    java 对象的序列化与反序列化

    Java对象的序列化和反序列化是Java编程中一项重要的技术,主要用于将对象的状态转换为字节流,以便存储或在网络上传输。这一过程对于理解Java的IO操作、持久化数据以及实现分布式通信等场景非常关键。 首先,我们来...

    Java序列化的机制和原理

    Java序列化是Java平台提供的一种将对象转换为字节流,以便存储、在网络上传输或者在后续时间重新创建相同对象的机制。这是Java编程中一个非常重要的概念,尤其是在分布式环境和持久化存储中。让我们深入探讨一下Java...

    Java对象序列化标准最新版

    ### Java对象序列化标准知识点详解 #### 一、系统架构概览 **1.1 概览** Java 对象序列化是一种将Java对象的...以上内容涵盖了Java序列化标准的关键知识点,深入了解这些概念有助于更好地理解和应用Java序列化技术。

    java serializable 序列化与反序列化

    **一、Java序列化** 1. **什么是序列化**:序列化是将对象的状态(属性和成员变量)转换为可以存储或传输的数据格式的过程。在Java中,通常是将对象转换为字节数组,以便写入磁盘或通过网络发送。 2. **为什么需要...

    java 常用序列化和反序列化框架使用demo

    Java序列化和反序列化是将对象的状态转换为字节流和从字节流恢复对象状态的过程,这对于数据持久化、网络传输以及跨进程通信等场景至关重要。在Java中,`java.io.Serializable`接口用于标记一个类是可序列化的。然而...

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

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

Global site tag (gtag.js) - Google Analytics