【问题标题】:How to use JarOutputStream to create a JAR file?如何使用 JarOutputStream 创建 JAR 文件?
【发布时间】:2010-11-19 20:31:16
【问题描述】:

如何使用java.util.jar.JarOutputStream 以编程方式创建 JAR 文件?我的程序生成的 JAR 文件看起来是正确的(它提取得很好)但是当我尝试从中加载一个库时,Java 抱怨它找不到明确存储在其中的文件。如果我提取 JAR 文件并使用 Sun 的 jar 命令行工具重新压缩它,则生成的库可以正常工作。简而言之,我的 JAR 文件有问题。

请说明如何以编程方式创建 JAR 文件,并附上清单文件。

【问题讨论】:

  • 也许你应该展示你当前的(非工作)解决方案

标签: java jar


【解决方案1】:

下面是一些使用 JarOutputStream 创建 JAR 文件的示例代码:

【讨论】:

  • 我已经这样做了。事实上,您引用的示例未能指出必须在目录名称上显式 putNextEntry() 或调用 JarOutputStream.closeEntry()。肯定有其他问题。
  • 啊,好的。在没有看到任何代码的情况下提供更好的解决方案有点困难,所以我只是指出了那个参考。不过很高兴你想通了。
  • 感谢您的帮助。谢谢!
【解决方案2】:

原来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 {
    String name = source.getPath().replace("\\", "/");
    if (source.isDirectory()) {
        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);
        }
    } else {
        JarEntry entry = new JarEntry(name);
        entry.setTime(source.lastModified());
        target.putNextEntry(entry);
        try (BufferedInputStream 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();
        }
    }
}

【讨论】:

  • 这些“怪癖”实际上是 zip 规范的一部分(jar 文件只是带有清单和不同扩展名的 zip 文件)。不过,我同意它应该记录在 API 文档中 - 我建议打开一个问题 (bugs.sun.com/bugdatabase)
  • 更重要的是,如果您传入错误类型的斜线或通过自动转换它们,API 应该通过引发异常来防止您创建无效的 ZIP/JAR 文件。对于以斜线结尾的目录,绝对应该记录在案,因为无法自动更正它。我提交了错误报告,但尚未被接受。
  • 仅供参考 - 压缩规范:pkware.com/documents/casestudies/APPNOTE.TXT - 搜索“文件名:(变量)”。
  • @Gili API 不能“阻止”你使用“错误”的斜线;因为,“错误的”斜杠是文件名中的有效字符。并非所有操作系统都将“\”识别为目录分隔符,并且那些不允许(不包括目录)文件名使用“he\he\he”的操作系统。
  • 这个程序结果奇怪的输出 $ ls MyClass.class output.jar 使用这个程序我创建了 jar output.jar $ jar tf output.jar META-INF/MANIFEST.MF /Users/aninath/Documents /workspace/interview/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/mywebapp2/tmp/ /Users/aninath/Documents/workspace/interview/.metadata/.plugins/ org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/mywebapp2/tmp/MyClass.class /Users/aninath/Documents/workspace/interview/.metadata/.plugins/org.eclipse.wst.server。核心/tmp0/work/Catalina/localhost/mywebapp2/tmp/output.jar
【解决方案3】:

还有一个“怪癖”需要注意:所有 JarEntry 的名称都不应以“/”开头。

例如:清单文件的 jar 条目名称是“META-INF/MANIFEST.MF”而不是“/META-INF/MANIFEST.MF”。

所有 jar 条目都应遵循相同的规则。

【讨论】:

  • 这应该作为对已接受答案的评论发布,因为它没有回答主要问题。
【解决方案4】:

您可以使用以下代码进行操作:

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 变量:名称和格式

【讨论】:

  • 接受的答案还没有说什么?
  • 接受的答案使用的代码比我的多。我想,当你能写一点的时候,你不会想写很多代码。
  • 公平地说,您的答案包含的代码更少,因为它的代码更少。接受的答案包含将输入按摩到 JarOutputStream 预期格式的代码。如果您在 Windows 下运行或目录名称不以斜杠结尾,您的代码将静默失败。
  • 用这个获取 ClassFormatError - 虽然使用接受的答案是可行的。
  • 如果中间抛出异常,流不会关闭。您必须使用 try-finally 或 try-with-resources 来保证流将被关闭。
【解决方案5】:

这个答案将解决相对路径问题。

private static void createJar(File source, JarOutputStream target) {
        createJar(source, source, target);
    }

    private static void createJar(File source, File baseDir, JarOutputStream target) {
        BufferedInputStream in = null;

        try {
            if (!source.exists()){
                throw new IOException("Source directory is empty");
            }
            if (source.isDirectory()) {
                // For Jar entries, all path separates should be '/'(OS independent)
                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()) {
                    createJar(nestedFile, baseDir, target);
                }
                return;
            }

            String entryName = baseDir.toPath().relativize(source.toPath()).toFile().getPath().replace("\\", "/");
            JarEntry entry = new JarEntry(entryName);
            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();
        } catch (Exception ignored) {

        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception ignored) {
                    throw new RuntimeException(ignored);
                }
            }
        }
    }

【讨论】:

    【解决方案6】:

    好的,根据要求,这里是 Gili 的代码,修改为使用相对路径而不是绝对路径。 (将“inputDirectory”替换为您选择的目录。)我刚刚测试了它,但如果它不起作用,请让我知道。

       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);
          File inputDirectory = new File("inputDirectory");
          for (File nestedFile : inputDirectory.listFiles())
             add("", nestedFile, target);
          target.close();
       }
    
       private void add(String parents, File source, JarOutputStream target) throws IOException
       {
          BufferedInputStream in = null;
          try
          {
             String name = (parents + source.getName()).replace("\\", "/");
    
             if (source.isDirectory())
             {
                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(name, nestedFile, target);
                return;
             }
    
             JarEntry entry = new JarEntry(name);
             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();
          }
       }
    

    【讨论】:

      猜你喜欢
      • 2012-07-10
      • 1970-01-01
      • 2014-06-12
      • 2013-08-01
      • 1970-01-01
      • 2017-02-09
      • 2013-09-28
      • 2012-04-24
      • 1970-01-01
      相关资源
      最近更新 更多