熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> JSP教程 >> 正文

用 One-JAR 簡化應用程序交付

2013-11-15 11:34:33  來源: JSP教程 

  如果您曾經試圖把 Java 應用程序交付為單一的 Java 檔案文件(JAR 文件)那麼您很有可能遇到過這樣的需求在構建最終檔案文件之前要展開支持 JAR 文件(supporting JAR file)這不但是一個開發的難點還有可能讓您違反許可協議在本文中Tuffs 向您介紹了 OneJAR 這個工具它使用定制的類裝入器動態地從可執行 JAR 文件內部的 JAR 文件中裝入類
  有人曾經說過歷史總是在不斷地重復自身首先是悲劇然後是鬧劇 最近我第一次對此有了親身體會我不得不向客戶交付一個可以運行的 Java 應用程序但是我已經交付了許多次它總是充滿了復雜性在搜集應用程序的所有 JAR 文件為 DOS 和 Unix(以及 Cygwin)編寫啟動腳本確保客戶端環境變量都指向正確位置的時候總是有許多容易出錯的地方如果每件事都能做好那麼應用程序能夠按它預期的方式運行但是在出現麻煩時(而這又是常見的情況)結果就是大量時間耗費在客戶端支持上
  
  最近與一個被大量 ClassNotFound 異常弄得暈頭轉向的客戶交談之後我決定自己再也不能忍受下去了所以我轉而尋找一個方法可以把我的應用程序打包到單一 JAR 文件中給我的客戶提供一個簡單的機制(比如 java jar)來運行程序
  
  努力的結果就是 OneJAR一個非常簡單的軟件打包解決方案它利用 Java 的定制類裝入器動態地從單一檔案文件中裝入應用程序所有的類同時保留支持 JAR 文件的結構在本文中我將介紹我開發 OneJAR 的過程然後告訴您如何利用它在一個自包含的文件中交付您自己的可以運行的應用程序
  
  OneJAR 概述
  在介紹 OneJAR 的細節之前請讓我首先討論一下我構建它的目的我確定一個 OneJAR 檔案文件應該是
  
  可以用 java jar 機制執行
  
  能夠包含應用程序需要的 所有 文件 —— 也就是說包括原始形式(未展開)的類和資源
  
  擁有簡單的內部結構僅僅用 jar 工具就可以被裝配起來
  
  對原來的應用程序不可見 —— 也就是說無需修改原來的應用程序就可以把它打包在 OneJAR 檔案文件內部
  
  問題和解決方案
  在開發 OneJAR 的過程中我解決的最大問題就是如何裝入包含在另外一個 JAR 文件中的 JAR 文件 Java 類裝入器 sunmiscLauncher$AppClassLoader(在 java jar 開始的時候出現)只知道如何做兩件事
  
  裝入在 JAR 文件的根出現的類和資源
  
  裝入 METAINF/MANIFESTMF 中的 ClassPath 屬性指向的代碼基中的類和資源
  而且它還故意忽略針對 CLASSPATH 的全部環境變量設置還忽略您提供的命令行參數 cp 所以它不知道如何從一個包含在其他 JAR 文件中的 JAR 文件裝入類或資源
  
  顯然我需要克服這個問題才能實現 OneJAR 的目標
  
  解決方案 展開支持 JAR 文件
  我為了創建單一可執行 JAR 文件所做的第一個嘗試顯然就是在可交付的 JAR 文件內展開支持 JAR 文件我們把可交付的文件稱為 mainjar假設有一個應用程序的類叫做 commainMain而且它依賴兩個類 —— comaA (在 ajar 中) 和 combB(在 bjar 中)那麼 OneJAR 文件看起來應該像這樣
  
  mainjar
  | com/main/Mainclass
  | com/a/Aclass
  | com/b/Bclass
  
  這樣最初來源於 ajar 文件的 Aclass 丟失了Bclass 也是如此雖然這看起來只是個小問題但卻會真正帶來問題我很快就會解釋為什麼
  
  OneJAR 和 FJEP
  最近發布的一個叫做 FJEP (FatJar Eclipse Plugin) 的工具支持在 Eclipse 內部直接構建扁平 JAR 文件 OneJAR 已經與 FatJar 集成在一起以支持在不展開 JAR 文件的情況下嵌入 JAR 文件請參閱 參考資料 了解有關詳細內容
  
  把 JAR 文件展開到文件系統以創建一個扁平結構這可能非常耗時還需要使用 Ant 這樣的構建工具來展開和重新歸檔支持類
  
  除了這個小麻煩之外我很快又遇到了兩個與展開支持 JAR 文件有關的嚴重問題
  
  如果 ajar 和 bjar 包含的資源的路徑名相同 (比如說都是 logjproperties )那麼您該選哪個?
  
  如果 bjar 的許可明確要求您在重新發布它的時候不能修改它那您怎麼辦?您無法在不破壞許可條款的前提下像這樣展開它
  我覺得這些限制為另外一種方法提供了線索
  
  解決方案 : MANIFEST ClassPath
  我決定研究 java jar 裝入器中的另外一種機制裝入的類是在檔案文件中一個叫做 METAINF/MANIFESTMF 的特殊文件中指定的通過指定稱為 ClassPath 的屬性我希望能夠向啟動時的類裝入器添加其他檔案文件下面就是這樣的一個 OneJAR 文件看起來的樣子
  
  mainjar
  | METAINF/MANIFESTMF
  | + ClassPath: lib/ajar lib/bjar
  | com/main/Mainclass
  | lib/ajar
  | lib/bjar
  
  說明與線索
  URLClassloader 是 sunmiscLauncher$AppClassLoader 的基類它支持一個相當神秘的 URL 語法讓您能夠引用 JAR 文件內部的資源這個語法用起來像這樣 jar:file:/fullpath/mainjar!/aresource
  
  從理論上講要獲得一個在 JAR 文件 內部 的 JAR 文件中的項您必須使用像 jar:file:/fullpath/mainjar!/lib/ajar!/aresource 這樣的方式但是很不幸這麼做沒有用JAR 文件協議處理器在找 JAR 文件時只認識最後一個 !/ 分隔符
  
  但是這個語法確實為我最終的 OneJAR 解決方案提供了線索……
  
  這能工作麼? 當我把 mainjar 移動到另外一個地方並試著運行它時好像是可以了為了裝配 mainjar 我創建了一個名為 lib 的子目錄並把 ajar 和 bjar 放在裡面不幸的是應用程序的類裝入器只從文件系統提取支持 JAR 文件而不能從嵌入的 JAR 文件中裝入類
  
  為了克服這一問題我試著用神秘的 jar:!/ 語法的幾種變體來使用 ClassPath(請參閱 說明和線索但是沒有一次成功我能 做的就只有分別交付 ajar 和 bjar 並把它們與 mainjar 一起放在文件系統中了但是這正是我想避免的那類事情
  
  進入 JarClassLoader
  此時我感到備受挫折我如何才能讓應用程序從它自己的 JAR 文件中的 lib 目錄裝入它自己的類呢?我決定應當創建定制類裝入器來承擔這個重任編寫定制類裝入器不是一件容易的事情但是實際上這個工作並沒有那麼復雜類裝入器對它所控制的應用程序有非常深刻的影響所以在發生故障的時候很難診斷和解釋故障雖然對於類裝入的完整處理超出了本文的范圍(請參閱 參考資料)我還是要介紹一些基本概念好保證您能從後面的討論中得到最大收獲
  
  裝入類
  當 JVM 遇到一個對象的類未知的時候就會調用類裝入器類裝入器的工作是找到類的字節碼(基於類的名稱)然後把這些字節傳遞給 JVMJVM 再把這些字節碼鏈接到系統的其余部分使得正在運行的代碼可以使用新裝入的類JDK 中關鍵的類是 javalangClassloader 以及 loadClass 方法摘要如下
  
  public abstract class ClassLoader {
  
  protected synchronized Class loadClass(String name boolean resolve)
  throws ClassNotFoundException {}
  }
  
  ClassLoader 類的主要入口點是 loadClass() 方法您會注意到 ClassLoader 是一個抽象類但是它沒有聲明任何抽象方法這樣關於 loadClass() 方法是不是要關注的方法一點線索也沒留下實際上它不是 要關注的主方法回到過去的好時光看看 JDK 的類裝入器可以看到 loadClass() 是您可以有效擴展類裝入器的惟一地方但是從 JDK 最好讓類裝入器單獨做它所做的工作即以下工作
  
  檢查類是否已經裝入
  檢查上級類裝入器能否裝入類
  調用 findClass(String name) 方法讓派生的類裝入器裝入類
  ClassLoaderfindClass() 的實現是拋出一個新的 ClassNotFoundException 異常並且是我們實現定制類裝入器時要考慮的第一個方法
  
  JAR 文件何時不是 JAR 文件?
  為了能夠裝入在 JAR 文件內部 的 JAR 文件中的類(這是關鍵問題您可以回想起來)我首先必須能夠打開並讀取頂層的 JAR 文件(上面的 mainjar 文件)現在因為我使用的是 java jar 機制所以 javaclasspath 系統屬性中的第一個(也是惟一一個)元素是 OneJAR 文件的完整路徑名!用下面的代碼您可以得到它
  
  jarName = SystemgetProperty(javaclasspath);
  
  我接下來的一步是遍歷應用程序的所有 JAR 文件項並把它們裝入內存如清單 所示
  
  清單 遍歷查找嵌入的 JAR 文件
  
  JarFile jarFile = new JarFile(jarName);
  Enumeration enum = jarFileentries();
  while (enumhasMoreElements()) {
  JarEntry entry = (JarEntry)enumnextElement();
  if (entryisDirectory()) continue;
  String jar = entrygetName();
  if (jarstartsWith(LIB_PREFIX) || jarstartsWith(MAIN_PREFIX)) {
  // Load it!
  InputStream is = jarFilegetInputStream(entry);
  if (is == null)
  throw new IOException(Unable to load resource / + jar + using + this);
  loadByteCode
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19228.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.