摘要
這篇技巧顯示了如何將一個不可獲取的Java檔案(JAR)變成可獲取的,而沒必要直接操作列表文件。你將學習開發一個短程序能使任何JAR用Java –jar命令運行或在一個象Widnows操作系統上用雙擊操作使其運行。
你能夠容易地將一個應用程序的整個類及資源打包進一個Java檔案(JAR)。實際上,這是jar文件的其中一個目的。另一個目的是讓用戶容易執行存貯在檔案中的應用程序。為什麼當它們能夠作為第一類正確依靠本地執行時,jar文件在Java世界中仍僅作為檔案功能被看成第二類成員?
要執行一個jar文件,你可以使用java命令中的-jar選項。例如,說你有一個可獲取的jar文件叫myjar.jar,這是因為這個文件是可獲取的,你能夠象這樣執行它:Java -jar myjar.jar.。
換句話說,當在一個象微軟Windows操作系統上安裝的Java運行環境(JRE)中,你能夠通過雙擊JVM相關的jar文件來運行應用程序。這些JARs就一定是可獲取的。
問題是:你怎樣使一個JAR可獲取?
清單文件和main-類入口
在多數JARs中,一個叫作MANIFEST.MF的文件存貯在一個叫作META-INF的目錄中。在那些文件中,一個叫作Main-類的專門入口告訴Java –jar命令哪個類將要被執行。
問題是你必須自己正確地將這些專門的入口添加到清單文件中——它必須在一個特定的地方並且具有一種特定的格式。然而,我們其中一些人不喜歡編輯配置文件。
讓API為你作這些
自從Java 1.2以來,一個叫java.util.jar的包讓你與jar文件一起工作。(注意:它是在Java.util.zip包上建立的),jar包讓你很容易地經由Manifest類操作專門的清單文件。
讓我們用這個API寫一個程序。首先,這個程序必須了解三件事情:
1.我們希望使能夠被獲取的JAR
2.我們希望被執行的main類(這個類必須存在於JAR中)
3.我們輸入的新JAR的名稱,因為我們不能簡單地覆蓋文件
寫程序
上面的列表將組成我們程序的參數。基於這一點,讓我們為這個程序選擇一個合適的名稱。MakeJarRunnable 怎麼樣?
檢查main的參數
假設我們的主入口點是一個標准的main(String[])方法。我們首先應該在這裡檢查程序的參數:
if (args.length != 3) {
System.out.println("Usage: MakeJarRunnable "
+ "
");
System.exit(0);
}
請注意參數列表是怎樣說明的,這對於下面的代碼是十分重要的。參數順序及內容在這裡沒有被設置,然而,假如你要改變它們記住相應地修改其它代碼。
訪問JAR和它的清單文件
首先,我們必須創建一些了解JAR和清單文件的對象:
//創建JarInputStream對象並獲取它的清單
JarInputStream jarIn = new JarInputStream(new FileInputStream(args[0]));
Manifest manifest = jarIn.getManifest();
if (manifest == null) {
//如果沒有清單存在,下面代碼將被執行
manifest = new Manifest();
}
設置Main-類的屬性
我們將Main-類入口放進清單文件的main屬性部份。一旦我們從清單對象中獲得這個屬性設置,我們就能設置適當的main類。然而,如果一個Main-類屬性已經存在於原JAR 中怎麼辦?這個程序會簡單地輸出一個警告並退出。也許我們能夠加一個命令行參數告訴程序用新的值代替先前存在的那個:
Attributes a = manifest.getMainAttributes();
String oldMainClass = a.putValue("Main-Class", args[1]);
//如果一個舊值存在,告訴用戶並退出
if (oldMainClass != null) {
System.out.println("Warning: old Main-Class value is: "
+ oldMainClass);
System.exit(1);
}
輸出新JAR
我們需要創建一個新jar文件,所以我們必須使用JarOutputStream類。注意:我們必須確保輸出和輸入不會使用同一文件。換句話說,也許程序應該考慮兩個jar文件一樣的情況並提示用戶是否他希望覆蓋原文件。然而,我將這作為讀者的練習保留。開始代碼!
System.out.println("Writing to " + args[2] + "...");
JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(args[2]),
manifest);
我們必須從輸入JAR到輸出JAR寫每一個入口,因此重申上述入口:
//創建一個讀緩沖以便從輸入處傳輸數據
byte[] buf = new byte[4096];
//重申入口
JarEntry entry;
while ((entry = jarIn.getNextJarEntry()) != null) {
//從舊JAR中排出清單文件
if ("META-INF/MANIFEST.MF".equals(entry.getName())) continue;
//為輸出JAR寫一個入口
jarOut.putNextEntry(entry);
int read;
while ((read = jarIn.read(buf)) != -1) {
jarOut.write(buf, 0, read);
}
jarOut.closeEntry();
}
//刷新並關閉所有流
jarOut.flush();
jarOut.close();
jarIn.close();
完成程序
當然,我們必須將這段代碼放到一個類的main方法中,並且使用一個適當的輸入聲明設置。源程序部分提供完整程序。
使用范例
讓我們用一個例子將這段程序投入實際使用。假設你已經有了一個應用程序其main入口點是在一個叫HelloRunnableWorld的類中(這是類的全稱)。同時假設你已經創建了一個叫myjar.jar的JAR文件,包含整個應用程序。象下面一樣在這個jar文件中運行MakeJarRunnable:
Java MakeJarRunnable myjar.jar HelloRunnableWorld myjar_r.jar
象前面提到的一樣,再次注意我是怎樣給參數列表排序的。如果你忘記了順序,正好沒有使用參數運行這個程序,它會響應一個用法消息。
試著在myjar.jar上運行Java -jar 命令,然後在myjar_r.jar上運行。注意不同的地方!這樣做了以後,在每一個JAR中浏覽清單文件(META-INF/MANIFEST.MF)。(你可以在源代碼中找到這兩個JARs)
這有一個建議:試著將MakeJarRunnable程序放進一個可獲取的JAR中!
運行
通過雙擊或使用一個簡單命令運行一個JAR總比不得不將其包含在你的類路徑中並運行一個特別的main類要方便。要幫助你做到這點,JAR規范為JAR的清單文件提供了一個Main-類屬性。這裡我所介紹的程序讓你使用Java的JAR API很輕松地操作這一屬性並使你的JARs可獲取。