摘要 通過構建一個能夠把Java類裝載隔離到一個指定的jar檔案中的類裝載組件容器架構,你可以確保運行時刻會裝載你期望的組件版本。
Java的類裝載架構強有力且具有靈活性。它允許應用程式存取類庫而不必連結到靜態"include"檔案。代之的是,它能夠從指定位置裝載包含庫類和資源的檔案檔案,例如由CLASSPATH環境變數所定義的目錄和網路位置。由系統來動態地解析對類和資源的運行時刻參考,從而簡化了更新和版本發行。然而,每一個庫都有其自己的依賴性集合-並且由開發人員和發布人員來保證他們的應用程式適當地參考正確的版本。遺憾的是,預設的類裝載系統和特定依賴性的結合可能並且確實會導致錯誤、系統崩潰甚至於更糟糕的情況發生。
本文中,我將向你建議一個實作類別裝載的容器架構,從而解決這些問題。
一、Java Classpath
Java根據環境屬性/變數CLASSPATH來指定運行時刻用來尋找類和其它資源的路徑。你可以通過設定CLASSPATH環境變數或使用Java命令列選項--classpath來定義CLASSPATH屬性。
典型地,一個Java運行時刻以下面順序尋找和載入類:
1. 在bootstrap類列表中的類-這些是體現Java平台的類,例如在rt.jar中的類。
2. 出現在擴充類列表中的類-這些類使用擴充機制架構來擴充Java平台,使用位於運行時刻環境的/lib/ext目錄下的檔案檔案(.jar,.zip,等等。)。
3. 使用者類-這些類不使用-classpath命令列選項或CLASSPATH環境變數標識的擴充機制架構。
二、檔案與Classpath
一個檔案.jar或.zip檔案可以包括一個manifest檔案-它們包含能夠用於提供檔案資訊,設定檔案屬性,等等的入口。這個manifest檔案還可以通過包括一個名為Class-Path的入口(它包含一個檔案和目錄列表)來擴充classpath。JDK 1.3中引入了Class-Path manifest入口用於指定可選的據需要可以載入的jar檔案和目錄。下面是一個Class-Path入口的例子:
Class-Path: mystuff/utils.jar
mystuff/logging.jar mylib/
Java提供了一種可擴充模型用於指定裝載類的位置和檔案清單。然而,由此也引發了一些問題,例如,一個不同版本的庫可能存在於classpath中-這超出一個執行類所期望的結果。
三、Classpath版本衝突
在Java中,一個類的運行時刻標識是由通過其完全限定名字來定義的(在類名之前的包名,有時被作為FQN),所有這些都添加到裝載類的相關裝載器的ID。這樣以來,由多個類載入器載入的一個類的每一個執行個體都將被當作是Java運行時刻的一個單獨的實體。這意味著,運行時刻能夠在任何時間裝載同一個類的多個版本。這是一種非常有力和相當靈活的特徵;然而,如果一位開發人員不認真地使用的話,某些副作用可能會令他疑惑不解。
可以設想,你在開發一個公司專屬應用程式程式-它使用類似語義從多種源存取資料,例如一個檔案系統和一個資料庫。許多這種類型的系統都暴露一個資料存取層-通過抽象類別似資料來源的資料存取對象(DAO)。現在,設想你裝載一個新版本的一個資料庫DAO,使用一種略微不同的API來滿足一個DAO用戶端的新特徵的要求-但是你仍然需要舊式的DAO以便適合於其它還沒有為這種新的API準備好的用戶端。在典型的運行時刻環境下,這種新的DAO將簡單地替換舊的版本並且所有的新執行個體都將從新版本中建立。然而,如果在不停止運行時刻環境的前提下發生更新,那麼任何已經存在的舊DAO的執行個體將與該新DAO的任何執行個體一起駐留於記憶體中-當建立這些新執行個體時。這已經足已令人疑惑了。更為糟糕的是,一位DAO客戶期望建立一箇舊版本的DAO的執行個體,但是實際上得到一個具有已改變的API的新版本的執行個體。正如你所見,這可能會帶來一些有趣的挑戰。
為了確保穩定性和安全性,調用代碼必須能夠指明它想使用的類的正確版本。為此,你可以建立一個類載入器,組件容器模型並且使用一些簡單的類載入技術。