摘要
本文展示如何將不可運行的JAR變為可運行的,並且不用直接操作manifest文件。你將學會開發一個短小的程序使得任何JAR文件都可以使用java -jar命令或者通過在像Windows上雙擊而運行。
你可以將一個應用的所有類和資源打包到一個JAR文件中。實際上,那就是jar文件的一個目的。另外一個目的是讓用戶可以非常容易的執行存儲在JAR文件中的應用,那麼為什麼當他們可以成為一等公民而和本機可執行程序等同的時候,我們為什麼要讓他們只承擔包的功能而成為java世界中的二等公民呢?
要執行一個jar文件,你可以使用java命令的-jar選項。例如你有一個可運行的文件名為myjar.jar的JAR文件,因為它是可運行的,你可以像這樣執行它:java -jar myjar.jar
另外,當JRE安裝在像Windows這樣的操作系統上時,將jar文件和JVM關聯後你就可以雙擊他們運行應用了。這些JAR必須是可運行的。
問題是:你如何讓一個JAR是可運行的?
manifest文件和Main-Class條目
在大部分JAR文件中,META-INF目錄下會有一個MANIFEST.MF文件,在那個文件中有一個特殊的條目Main-Class,它告訴java -jar命令去執行那麼類。
問題是你必須自己恰當的將這個特殊條目加到manifest文件中:它必須位於特定的位置並且必須符合特定的格式,然而有些人不喜歡編輯配置文件。
讓API幫你做
從Java 1.2開始引人的java.util.jar包可以讓你操作jar文件(注意:它建立在java.util.zip包的基礎上)。更確切的說法是,java.util.jar可以讓你通過Manifest類非常容易的操作那個特殊的manifest文件。
讓我們編寫一個程序使用那個API。首先這個程序必須知道三件事情:
1. 我們希望可以執行的JAR
2. 我們希望執行的主類(這個類必須存在於JAR內)
3. 新的JAR文件的文件名,因為我們不應該簡單的覆蓋那些文件
編寫程序
上面的列表將會構成我們的程序的參數,基於這一點,讓我們為這個應用挑選一個合適的名字。MakeJarRunnable聽起來如何?
檢查main的參數
假設我們的main入口是一個標准的main(String[])方法,我們首先應該檢查程序的參數:
if (args.length != 3) {
System.out.println("Usage: MakeJarRunnable "
+ "<jar file> <Main-Class> <output>");
System.exit(0);
}
請注意參數列表是如何被解釋的,因為這對於後面的代碼是非常重要的。參數的順序和內容並不是硬性設置的,但是如果你改變它們也要記得適當的修改其他的代碼。
訪問JAR和它的manifest文件
首先我們必須創建一些知道JAR和manifest文件的對象:
//Create the JarInputStream object, and get its manifest
JarInputStream jarIn = new JarInputStream(new FileInputStream(args[0]));
Manifest manifest = jarIn.getManifest();
if (manifest == null) {
//This will happen if no manifest exists
manifest = new Manifest();
}
設置Main-Class屬性
我們將Main-Class條目放到manifest文件的主要屬性部分。一旦我們從manifest對象獲得了這個屬性集我們就可以設置適當的主類。然而如果一個Main-Class屬性已經存在於原來的JAR時怎麼辦?這個程序簡單的打印一個警告並退出。或許我們可以增加一個命令行參數告訴程序用新的值替換已經存在的那個值.
Attributes a = manifest.getMainAttributes();
String oldMainClass = a.putValue("Main-Class", args[1]);
//If an old value exists, tell the user and exit
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中,因為對那些條目迭代:
//Create a read buffer to transfer data from the input
byte[] buf = new byte[4096];
//Iterate the entries
JarEntry entry;
while ((entry = jarIn.getNextJarEntry()) != null) {
//Exclude the manifest file from the old JAR
if ("META-INF/MANIFEST.MF".equals(entry.getName())) continue;
//Write the entry to the output JAR
jarOut.putNextEntry(entry);
int read;
while ((read = jarIn.read(buf)) != -1) {
jarOut.write(buf, 0, read);
}
jarOut.closeEntry();
}
//Flush and close all the streams
jarOut.flush();
jarOut.close();
jarIn.close();
完整程序
當然我們必須將這些代碼放到一個類裡面的main方法裡面,並且具有合適的import聲明。
使用范例
讓我們用一個范例來使用這個程序。假設你有一個應用其main入口點是類HelloRunnableWorld(這個是它的全類名,也就是包含包名),同樣假設你已經創建了一個名字為myjar.jar的JAR,包含整個應用。對於這個jar,我們像這樣運行MakeJarRunnable:
java MakeJarRunnable myjar.jar HelloRunnableWorld myjar_r.jar
再強調一次,就像早先提到的,注意參數列表的順序。如果忘記了順序,以無參的形式運行程序它就會告訴你使用信息。
使用java -jar命令運行myjar.jar和myjar_r.jar,注意它們的差異。完成這些之後,查看一下它們的manifest文件(META-INF/MANIFEST.MF)。
這裡有一個建議:將MakeJarRunnable制作成一個可以運行的JAR!
運行它
通過雙擊一個JAR或者使用簡單的命令總是比將它包含在你的classpath並運行特定的main類方便。為了幫助你作到這一點,JAR規范為JAR的manifest文件提供了一個Main-Class屬性。我在這裡提出的這個程序讓你利用Java的JAR API非常容易的操作這個屬性並制作你自己的可運行的JAR。
本文配套源碼