http://www.pcdog.com/edu/java/20/11/w240538.html
序列化對象在Java中
主要有兩個目的,一個是鈍化儲存物件,另一個是通過網路傳輸對象。
後者是移動或者遠程計算的基礎。前者比較好辦,Object Storage Service之後,往往由同一個程式再讀出,
對象在解析的時候不存在類載入的問題。後者比較麻煩,接收序列化對象的一端往往同發送端的類載入器環境不一樣,很有可能找不到發送端才有的類代碼,因此也
就無法還原序列化對象,造成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就是描述這個機制的。這樣帶來的開發模式時,程式員按照
案頭應用的模式開發軟體,可以不用修改地部署到用戶端/伺服器端的這種模式上,將複雜的公司專屬應用程式的開發和部署變成了簡單的案頭軟體的開發。
當然在寫這個平台時候,我就遇到這個困惑,如何將應用程式的Runspace同平台的Runspace分離開,如何將A程式的Runspace同B程式的Runspace分離開?開始由於
這個問題沒有搞清楚,結果使用了預設的ObjectInputStrea類,使得平台進行序列化和還原序列化過程中老是使用平台的類載入器,當要序列化和反
序列化應用程式的類對象時,n多的ClassNotFoundException就出來了。
可能我的腦筋當時就是轉不過來了,死活沒有想到用自訂的ObjectInputStream來取代JDK的ObjectInputStream,就是在
琢磨如何將ObjectInputStream的調用棧的類載入器替換成自己的。後來想清楚了,在使用自訂的ObjectInputStream的情況
是不可能的。今天天氣不錯,我喝了杯回到座位上時,突然想起來為什麼不自己寫ObjectInputStream呢,自己來控制序列化的過程,起碼是部分
控制就已經足夠,這樣不就可以替換自己的了嗎?^_^
有了這個想法,實現就很容易了。我趕緊開啟JDK的src.zip,研究ObjectInputStream,看哪些方法是可以覆蓋來改變行為的。結果
是,其實這個問題很簡單,只是我太固執。人家已經明明白白的寫好了如果你繼承這個方法會怎麼怎麼樣改變行為,還有例子。
人要是固執起來,真是不可理喻。我差點沒有撞到南牆上,總算還想起來可以繞過南牆過去。
如何解決,很簡單,覆蓋ObjectInputStream中的resolveClass方法。這個方法就是在還原序列化過程中讀取類描述後載入類而使用的。這兒你只要給它返回它的描述需要的類就行了,至於你怎麼載入,全部在你的控制之下。
看了這個東西,順便看了一下這個方法對應的ObjectOutputStream方法是annotateClass,它的意思是在序列化改類時如何描述改
類。預設的實現是空。你可以覆蓋它,加上你自己的東西,比如乾脆將類代碼放在這兒,防止對方找不到類。在ObjectInputStream的自訂類
中,你就可以將這個寫類代碼解析了使用。代碼隨著對象一起傳輸,再一次形象表述了所謂移動計算。