Java 如何使用JarOutputStream创建JAR文件?

Java 如何使用JarOutputStream创建JAR文件?,java,jar,Java,Jar,如何使用java.util.JAR.JarOutputStream以编程方式创建JAR文件?我的程序生成的JAR文件看起来是正确的(它提取得很好),但当我尝试从中加载库时,Java抱怨它找不到明确存储在其中的文件。如果我提取JAR文件并使用Sun的JAR命令行工具重新压缩它,那么生成的库就可以正常工作。简而言之,我的JAR文件有问题 请解释如何以编程方式创建JAR文件,包括清单文件。以下是一些使用JarOutputStream创建JAR文件的示例代码: 事实证明,JarOutputStrea

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


请解释如何以编程方式创建JAR文件,包括清单文件。

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


事实证明,
JarOutputStream
有三个未记录的怪癖:

  • 目录名必须以“/”斜杠结尾
  • 路径必须使用“/”斜杠,而不是“\”
  • 条目不能以“/”斜杠开头
  • 以下是创建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();
      }
    }
    
    还有另一个需要注意的“怪癖”:所有JarEntry的名字都不应该以“/”开头

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


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

    您可以使用以下代码:

    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文件的路径


    文件
    变量:名称和格式

    此答案将解决相对路径问题

    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);
                    }
                }
            }
        }
    

    根据请求,这里是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();
          }
       }
    

    我已经在做了。事实上,您引用的示例没有指出必须对目录名显式地putnextry()或调用JarOutputStream.closeEntry()。一定是出了什么事。啊,好的。在没有看到任何代码的情况下,要提供更好的解决方案有点困难,所以我只是向您指出了那个参考。很高兴你找到了答案。谢谢你的帮助。非常感谢。也许您应该展示您当前的(非工作)解决方案这些“怪癖”实际上是zip规范的一部分(jar文件只是带有清单和不同扩展名的zip文件)。我同意应该将其记录在API文档中,不过-我建议打开一个问题(),更重要的是,API应该防止您创建无效的ZIP/JAR文件,如果您传入了错误类型的斜杠,则会抛出异常,或者自动转换它们。对于以斜杠结尾的目录,应该明确记录它,因为没有办法自动更正它。我提交了一份bug报告,但还没有被接受。仅供参考-Zip spec:-搜索“文件名:(变量)”@Gili api无法“阻止”您使用“错误”的斜杠;因为,“错误”斜杠是文件名中的有效字符。并非所有操作系统都将“\”识别为目录分隔符,以及那些不允许(不包括目录)文件名为“he\he\he”的操作系统。此程序生成奇怪的输出$ls MyClass.class output.jar使用此程序我创建了jar output.jar$jar tf output.jar META-INF/MANIFEST.MF/Users/aniath/Documents/workspace/interview/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/mywebapp2/tmp//Users/aniath/Documents/workspace/interview/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/mywebapp2/tmp/MyClass.class/Users/aniath/Documents/workspace/interview/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/mywebapp2/tmp/output.jar这又增加了什么,接受的答案没有已经说了吗?被接受的答案比我的答案使用了更多的代码。我认为,当你能写一点的时候,你不想写太多的代码。公平地说,你的答案包含更少的代码,因为它做的更少。接受的答案包含将输入转换为JarOutputStream所期望的格式的代码。如果您在Windows下运行,或者如果目录名不以斜杠结尾,代码将以静默方式失败。但是,使用接受的答案获取ClassFormatError是可行的。如果在这两者之间引发异常,则流不会关闭。您必须使用try finally或try with resources来保证流将被关闭。这应该作为对已接受答案的评论发布,因为它没有回答主要问题。