2009-08-15 116 views
49

如何使用java.util.jar.JarOutputStream以編程方式創建JAR文件?我的程序生成的JAR文件看起來是正確的(它提取的很好),但是當我嘗試從它加載一個庫時,Java抱怨它找不到明確存儲在其中的文件。如果我解壓縮JAR文件並使用Sun的jar命令行工具重新壓縮它,則生成的庫工作正常。總之,我的JAR文件出了問題。如何使用JarOutputStream創建JAR文件?

請解釋如何以編程方式創建JAR文件,並附帶清單文件。

+2

也許你應該表現出你目前(非工作)解決方案 – ChssPly76 2009-08-15 06:14:35

回答

82

事實證明,JarOutputStream有三個無證的怪癖:

  1. 目錄名稱必須結束用'/'斜槓。
  2. 路徑必須使用'/'斜線,而不是'\'
  3. 項目不能以'/'斜槓開頭。

下面是創建一個JAR文件的正確方法:

public void run() throws IOException 
{ 
    Manifest manifest = new Manifest(); 
    manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); 
    JarOutputStream target = new JarOutputStream(new FileOutputStream("output.jar"), manifest); 
    add(new File("inputDirectory"), target); 
    target.close(); 
} 

private void add(File source, JarOutputStream target) throws IOException 
{ 
    BufferedInputStream in = null; 
    try 
    { 
    if (source.isDirectory()) 
    { 
     String name = source.getPath().replace("\\", "/"); 
     if (!name.isEmpty()) 
     { 
     if (!name.endsWith("/")) 
      name += "/"; 
     JarEntry entry = new JarEntry(name); 
     entry.setTime(source.lastModified()); 
     target.putNextEntry(entry); 
     target.closeEntry(); 
     } 
     for (File nestedFile: source.listFiles()) 
     add(nestedFile, target); 
     return; 
    } 

    JarEntry entry = new JarEntry(source.getPath().replace("\\", "/")); 
    entry.setTime(source.lastModified()); 
    target.putNextEntry(entry); 
    in = new BufferedInputStream(new FileInputStream(source)); 

    byte[] buffer = new byte[1024]; 
    while (true) 
    { 
     int count = in.read(buffer); 
     if (count == -1) 
     break; 
     target.write(buffer, 0, count); 
    } 
    target.closeEntry(); 
    } 
    finally 
    { 
    if (in != null) 
     in.close(); 
    } 
} 
+14

這些'怪癖'實際上是zip規範的一部分(jar文件只是帶有清單和不同擴展名的zip文件)。我同意它應該記錄在API文檔中,但是 - 我建議打開一個問題(http://bugs.sun.com/bugdatabase/) – 2009-08-15 19:18:03

+5

更重要的是,API應該可以防止你通過創建無效的ZIP/JAR文件如果您傳遞了錯誤類型的斜槓或自動轉換它們,則拋出異常。對於以斜線結尾的目錄,由於無法自動糾正,所以應該記錄它。我提交了一個錯誤報告,但尚未被接受。 – Gili 2009-08-16 15:52:38

+0

經典的Sun/Oracle。關閉爲「不是bug」:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6873352 – Gili 2012-01-17 18:06:13

3

下面是一些示例代碼使用的JarOutputStream創建一個JAR文件:

+1

我已經在做這個。實際上,您引用的示例未能指出必須在目錄名稱上明確放置nextEntry()或調用JarOutputStream.closeEntry()。其他的一定是錯的。 – Gili 2009-08-15 05:39:43

+0

啊,好的。在沒有看到任何代碼的情況下提供更好的解決方案有點困難,所以我只是指出你的參考。很高興你知道了。 – ars 2009-08-15 07:53:20

+0

我感謝你的幫助。謝謝! – Gili 2009-08-15 17:15:57

9

還有一個「怪癖」注意:所有的JarEntry的名字不應該與「/」開頭。

例如:清單文件的jar條目名稱是「META-INF/MANIFEST.MF」,而不是「/META-INF/MANIFEST.MF」。

所有jar條目都應遵循相同的規則。

1

您可以使用此代碼做到這一點:

public void write(File[] files, String comment) throws IOException { 
    FileOutputStream fos = new FileOutputStream(PATH + FILE); 
    JarOutputStream jos = new JarOutputStream(fos, manifest); 
    BufferedOutputStream bos = new BufferedOutputStream(jos); 
    jos.setComment(comment); 
    for (File f : files) { 
     print("Writing file: " + f.toString()); 
     BufferedReader br = new BufferedReader(new FileReader(f)); 
     jos.putNextEntry(new JarEntry(f.getName())); 
     int c; 
     while ((c = br.read()) != -1) { 
      bos.write(c); 
     } 
     br.close(); 
     bos.flush(); 
    } 
    bos.close(); 
// JarOutputStream jor = new JarOutputStream(new FileOutputStream(PATH + FILE), manifest); 

} 

PATH變量:路徑JAR文件

FILE變量:名稱和格式

+0

這是什麼增加,接受的答案還沒有說? – Gili 2016-10-13 12:36:44

+0

接受的答案使用比我更多的代碼。我認爲,你可以寫一點時不想編寫太多的代碼。 – Muskovets 2017-12-10 18:06:04

+1

在所有適當的公平性中,您的答案包含的代碼較少,因爲它的確較少。接受的答案包含按照JarOutputStream預期的格式輸入的代碼。如果您在Windows下運行,或者目錄名稱不以斜線結尾,您的代碼將無提示失敗。 – Gili 2017-12-12 02:03:41