【问题标题】:JarFile#entries do not show "META-INF" as a JarEntry when creating jar with JarOutputStream使用 JarOutputStream 创建 jar 时,JarFile#entries 不会将“META-INF”显示为 JarEntry
【发布时间】:2017-02-09 12:10:25
【问题描述】:

根据链接:https://stackoverflow.com/a/1281295/1794012

我按照说明创建了一个jar文件,创建jar的源文件的输入目录如下,

  • 所以(目录)
  • so/some.txt(文件)

当我遍历 JarFile#entries 方法时,它会打印以下内容,

通过 JarOutputStream 创建 jar 时的 JarFile#entries 输出

META-INF/MANIFEST.MF
D:/so/
D:/so/some.txt

但是我使用 jar 工具创建了 jar 文件

使用简单的jar工具创建jar

jar -cvf so_commond.jar so so/some.txt
添加清单
添加:so/(in = 0) (out= 0)(stored 0 %)
添加:so/some.txt(in = 7) (out= 9)(deflated -2​​8%)

现在我使用 JarFile#entries 来迭代条目,以下是输出

jar工具创建jar时的JarFile#entries输出

META-INF/(JarOutputStream 创建 jar 时不存在)
META-INF/MANIFEST.MF
所以/
所以/some.txt

能否请您解释一下为什么jar条目META-INF仅在jar工具创建时显示,而jar在JarOutputStream创建时不显示?

代码:

public static void main(String[] args){
  run();
    for(Enumeration<JarEntry> e = jf.entries(); e.hasMoreElements();){
                       System.out.println(e.nextElement().getName());
        }
 }

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

    private static 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();
        }
    }

【问题讨论】:

    标签: java jar zip


    【解决方案1】:

    目录在 zip 文件格式中是可选的。使用zip 命令行工具时,可以使用--no-dir-entries 进行配置。

    【讨论】:

    • man zip 谈到-D/--no-dir-entries:“不要在 zip 存档中为目录创建条目。目录条目是默认创建的,因此它们的属性可以保存在 zip 存档中。 "所以我想这只是我是否对目录属性感兴趣(我不感兴趣)的问题。
    • (如果我不认识这个名字,我会奖励你的。)
    【解决方案2】:

    要获得类似于jar -cvf so_commond.jar so so/some.txt 命令产生的结果,您可以这样更改run 方法:

        public static void run() throws IOException {
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
        try ( JarOutputStream target = new JarOutputStream(new FileOutputStream("D:\\so.jar"))) {
            if (manifest != null) {
                ZipEntry e = new ZipEntry(MANIFEST_DIR);
                e.setTime(System.currentTimeMillis());
                e.setSize(0);
                e.setCrc(0);
                target.putNextEntry(e);
                e = new ZipEntry(MANIFEST_NAME);
                e.setTime(System.currentTimeMillis());
                target.putNextEntry(e);
                manifest.write(target);
                target.closeEntry();
            }
        add(new File("D:\\so"), target);
        target.close();
        }
    }
    

    以上代码需要以下定义:

        import static java.util.jar.JarFile.MANIFEST_NAME;
        // .. and in the Class
        static final String MANIFEST_DIR = "META-INF/";
    

    原因是 jar 工具有一个create 方法,它是这样操作的: https://github.com/geekerstar/OpenJdk-11/blob/a576af6f90dfa05a935b813d633d9c8aaa690f83/src/sun/tools/jar/Main.java#L847

     /**
     * Creates a new JAR file.
     */
    void create(OutputStream out, Manifest manifest) throws IOException
    {
        try (ZipOutputStream zos = new JarOutputStream(out)) {
            if (flag0) {
                zos.setMethod(ZipOutputStream.STORED);
            }
            // TODO: check module-info attributes against manifest ??
            if (manifest != null) {
                if (vflag) {
                    output(getMsg("out.added.manifest"));
                }
                ZipEntry e = new ZipEntry(MANIFEST_DIR);
                e.setTime(System.currentTimeMillis());
                e.setSize(0);
                e.setCrc(0);
                zos.putNextEntry(e);
                e = new ZipEntry(MANIFEST_NAME);
                e.setTime(System.currentTimeMillis());
                if (flag0) {
                    crc32Manifest(e, manifest);
                }
                zos.putNextEntry(e);
                manifest.write(zos);
                zos.closeEntry();
            }
            updateModuleInfo(moduleInfos, zos);
            for (Entry entry : entries) {
                addFile(zos, entry);
            }
        }
    }
    

    【讨论】:

    • 如果 shell 扩展明确列出它们,jar 似乎包含目录,如果您不寻找它(IMO),这不一定很明显。我有点好奇为什么你会想要目录条目(但不清楚)。
    【解决方案3】:

    当您使用命令行工具时,您将始终有两个单独的条目。当使用JarOutputStream 类时,您将永远拥有一个。原因如下:

    一方面,如果您查看JarOutputStream 类,您会发现该工具使用单个条目来表示META-INF/MANIFEST.MF

        public JarOutputStream(OutputStream out, Manifest man) throws IOException {
            super(out);
            if (man == null) {
                throw new NullPointerException("man");
            } else {
                ZipEntry e = new ZipEntry("META-INF/MANIFEST.MF");
                this.putNextEntry(e);
                man.write(new BufferedOutputStream(this));
                this.closeEntry();
            }
        }
    

    另一方面,正如 Franco Rondini 在他的回答中所说,jar 命令行工具插入两个单独的条目,一个用于META-INF 目录,一个用于MANIFEST.MF 文件:

    https://github.com/geekerstar/OpenJdk-11/blob/a576af6f90dfa05a935b813d633d9c8aaa690f83/src/sun/tools/jar/Main.java#L847

        void create(OutputStream out, Manifest manifest) throws IOException
        {
            try (ZipOutputStream zos = new JarOutputStream(out)) {
                if (flag0) {
                    zos.setMethod(ZipOutputStream.STORED);
                }
                // TODO: check module-info attributes against manifest ??
                if (manifest != null) {
                    if (vflag) {
                        output(getMsg("out.added.manifest"));
                    }
                    ZipEntry e = new ZipEntry(MANIFEST_DIR); //<<<<<<<<<<<< HERE
                    e.setTime(System.currentTimeMillis());
                    e.setSize(0);
                    e.setCrc(0);
                    zos.putNextEntry(e);
                    e = new ZipEntry(MANIFEST_NAME); //<<<<<<<<<<<<<<<<<<<< AND HERE
                    e.setTime(System.currentTimeMillis());
                    if (flag0) {
                        crc32Manifest(e, manifest);
                    }
                    zos.putNextEntry(e);
                    manifest.write(zos);
                    zos.closeEntry();
                }
                updateModuleInfo(moduleInfos, zos);
                for (Entry entry : entries) {
                    addFile(zos, entry);
                }
            }
        }
    

    因此两者之间的区别。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-19
      • 2010-12-31
      • 2012-07-10
      • 1970-01-01
      • 2017-06-22
      • 1970-01-01
      • 1970-01-01
      • 2012-10-26
      相关资源
      最近更新 更多