摘要
這個技巧闡明了如何不直接處理清單文件而將一個不能運行jar包轉換成一個可以執行的jar包。學會如何寫一段轉換jar包的程序,將你的jar包轉換成你能使用java -jar 命令運行jar包或象在windows系統上那樣通過雙擊來運行jar包。
你可以很容易地將一個應用的所有的類和資源打包到一個jar文件中去。事實上,這只是打包的一個原因。另一個原因是讓用戶很容易地執行包中的應用。那么在java的世界里,為什么jar是第二類公民――僅僅用作打包――當他們能成為第一類公民的時候,能和本地執行程序一樣嗎?
如果想執行jar文件,可以使用java命令的-jar選項。例如,你有一個可以運行的myjar.jar文件。因為該文件是可以執行的,所以你可以用如下命令執行之:java –jar myjar.jar。此外,安裝在windows JRE將會把jar文件和JVM關聯起來,以便通過雙擊來運行jar應用。
現在的問題是:如何把jar做成可以運行的包?
清單文件和主類條目
在大多數jar文件中,在目錄META-INF里有個叫MANIFEST.MF的文件。在該文件中,有一個叫Main-Class的特殊條目,它說明了java -jar命令執行的類。
問題是你必須正確地在清單文件中加入這個特殊的條目――它必須是在特定的位置和特定的格式。可是,好多人不喜歡修改配置文件。
用API修改改配置
從java1.2之后,一個叫java.util.jar的包可以操作jar文件(注:它架構在java.util.zip包之上)。Jar包能讓你通過Manifest類很容易地操作專門的清單文件。
讓我們使用這些API寫一個程序。首先,這個程序必須知道三件事:
- 我們想讓它執行的jar文件;
- 我們想執行的main類(這個文件必須存在於jar包中);
- 我們要輸出的新jar包的名稱,因為不是簡單地覆蓋原文件;
編寫代碼
上面的列表將組成我們程序的參數。在這里,讓我們選擇一個合適名字,MakeRunnable咋樣?
檢查送入main的參數
假設我們的main入口是標准的main(String[])方法。我們首先應該檢查程序的參數:
| if (args.length != 3) {
System.out.println("Usage: MakeJarRunnable "
+ "<jar file> <Main-Class>
<output>");
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-Class條目放到清單文件里main屬性部分。一旦我們從mainfest對象中得到這個屬性集,我們就能設置合適的main類。然而,如果Main-Class屬性存在於原始的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類。注意:我們必須確保輸出文件和輸入文件不相同。作為可選方案,應該考慮如果兩個文件同名,程序應該提示用戶是否覆蓋原始文件。我將這個作為練習留給讀者。下面是代碼。
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包,它包含了整個應用。在這個包上運行MakeJarRunnable,如下所示:
| java MakeJarRunnable myjar.jar HelloRunnableWorld myjar_r.jar
|
此外,如前所述,注意我是如何安排參數列表順序的。如果你忘記了順序,運行一下這個不帶參數的程序,它將會顯示一個用法信息。
試着用java -jar命令運行myjar.jar文件,之后在myjar_r.ja文件上。注意他們的不同!完成之后,研究在每一個jar包中的清單文件(META-INF/MANIFEST.MF)。
建議:試着將MakeJarRunnable制作成一個可以運行的Jar文件!
用它處理你想運行的jar包
通過雙擊或者使用簡單的命令運行一個jar包,總是要比把它放到 gagaghost
你可能也感興趣:深入淺出Java多線程(1)-方法 join 什么時候用Vector, 什么時候改用ArrayList?
