序列化对象在Java中 主要有两个目的,一个是钝化存储对象,另一个是通过网络传输对象。 后者是移动或者远程计算的基础。前者比较好办,对象存储之后,往往由同一个程序再读出, 对象在解析的时候不存在类加载的问题。后者比较麻烦,接收序列化对象的一端往往同发送端的类加载器环境不一样,很有可能找不到发送端才有的类代码,因此也 就无法反序列化对象,造成ClassNotFoundException。
Java中利用序列化机制进行移动/远程计算的一个机制是RMI。RMI客户端通过网络通信将序列化的参数对象传送给服务器端,服务器端将客户端传送来得 的参数反序列化之后,调用相应对象的方法,并将结果序列化之后传送给客户端,客户端收到序列化的结果后,再使用反序列化功能将结果对象还原返回给调用程 序。这是一般远程过程调用的基本原理,只是不同的远程过程调用采用的序列化和反序列化的方法不一样,更一般的远程过程调用系统比如Web服务(如SOAP 协议)或者Corba协议,则采用XML或者定义中间数据类型的模式,客户端和服务器端将调用参数和返回结果采用XML或者中间语言表达的方法实现类型系统的转化。
不像一般远程过程调用系统需要你自定义数据类型转化过程,Java的序列化机制将类描述和反射机制结合起来,实现了自动序列化的过程,降低了开发难度和复 杂度。为了能序列化和反序列化,序列化的双方必须知道(懂得)序列化的类的结构,这就是为甚么要在序列化和反序列化过程使用类加载器来加载所需的类。序列化流中保存了类的表述信息,比如类的名称,类的签名等,反序列化的一方在读取这些信息之后,就需要根据这些信息加载该类,以便通过反射生成对象,并将后续 的对象数据读取进来。
那么反序列化的一方如何加载对方的类呢?缺省的ObjectInputStream类获取调用栈上最近用户自定义的类加载器,如果没有用户自定义的类加载 器,则使用当前线程的ContextClassLoader。一般来说这个ContextClassLoader就是应用程序的 BootstrapClassLoader,它包括了JRE和应用程序的类路径。然而服务器方的类路经一般不可能覆盖客户端的类路径,这时就需要替换这个 类加载器,让它按照自定义的地方找类代码。
RMI替换了这个类加载器,它自定义的类加载器可以从指定的URL下载特定的类代码,并根据需要加载这个类,从而完成反序列化过程。所以熟悉RMI的朋友 就会发现为了实现一个RMI调用,过程是非常复杂的,你不仅要指定服务器端供应类代码的URL,还要指定客户端供应类代码的URL,毕竟服务器端和客户端 都不仅仅需要序列化,也需要反序列化,才能完成对象交换。代码从另一个位置,典型的是网络上,传输另一个地方被解析执行,这就是通常移动计算的来历。由于 代码是传自另一个位置,又会涉及到执行移动代码的安全问题。幸亏Java的安全系统设计的非常完美,它的模型完美的解决了移动计算的问题。
我最近帮朋友做的这个平台,一个基本机制就是远程过程调用。没有采用RMI机制,原因是RMI机制太过底层,对于一般Java程序员来说完成一个调用过程 非常痛苦。也没有采用封装RMI的形式,因为RMI的基本模型即接口和实现未免太过复杂,对于程序员要求还是过高。我们的原型是普通一个Java业务类, 通过自己写的动态代理封装出客户端和服务器端来。前面有篇文章:Java动态代理、ASM与AOP就是描述这个机制的。这样带来的开发模式时,程序员按照 桌面应用的模式开发软件,可以不用修改地部署到客户端/服务器端的这种模式上,将复杂的企业应用的开发和部署变成了简单的桌面软件的开发。
当然在写这个平台时候,我就遇到这个困惑,如何将应用程序的运行空间同平台的运行空间分离开,如何将A程序的运行空间同B程序的运行空间分离开?开始由于 这个问题没有搞清楚,结果使用了缺省的ObjectInputStrea类,使得平台进行序列化和反序列化过程中老是使用平台的类加载器,当要序列化和反 序列化应用程序的类对象时,n多的ClassNotFoundException就出来了。
可能我的脑筋当时就是转不过来了,死活没有想到用自定义的ObjectInputStream来取代JDK的ObjectInputStream,就是在 琢磨如何将ObjectInputStream的调用栈的类加载器替换成自己的。后来想清楚了,在使用自定义的ObjectInputStream的情况 是不可能的。今天天气不错,我喝了杯回到座位上时,突然想起来为什么不自己写ObjectInputStream呢,自己来控制序列化的过程,起码是部分 控制就已经足够,这样不就可以替换自己的了吗?^_^
有了这个想法,实现就很容易了。我赶紧打开JDK的src.zip,研究ObjectInputStream,看哪些方法是可以覆盖来改变行为的。结果 是,其实这个问题很简单,只是我太固执。人家已经明明白白的写好了如果你继承这个方法会怎么怎么样改变行为,还有例子。
人要是固执起来,真是不可理喻。我差点没有撞到南墙上,总算还想起来可以绕过南墙过去。
如何解决,很简单,覆盖ObjectInputStream中的resolveClass方法。这个方法就是在反序列化过程中读取类描述后加载类而使用的。这儿你只要给它返回它的描述需要的类就行了,至于你怎么加载,全部在你的控制之下。
看了这个东西,顺便看了一下这个方法对应的ObjectOutputStream方法是annotateClass,它的意思是在序列化改类时如何描述改 类。缺省的实现是空。你可以覆盖它,加上你自己的东西,比如干脆将类代码放在这儿,防止对方找不到类。在ObjectInputStream的自定义类 中,你就可以将这个写类代码解析了使用。代码随着对象一起传输,再一次形象表述了所谓移动计算。