你可以在没有首先拆包的情况下使用Pack200文件吗?

时间:2017-10-29 00:14:27

标签: java jar classloader

是否可以从Pack200创建的pack.gz文件中即时加载类(在内存中),而无需先将其解压缩到jar中?我能找到的所有示例都告诉我如何将其解压缩到.jar文件中,然后从.jar文件加载类。

1 个答案:

答案 0 :(得分:0)

是的,这是可能的。 Pack200.Unpacker.unpack方法写入JarOutputStream;如果你在后台线程中这样做,你可以使用管道将新的JarOutputStream连接到JarInputStream,并从中读取。

public JarInputStream readPackFile(Path packGzFile)
throws IOException {

    PipedInputStream pipeIn = new PipedInputStream();
    PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);

    ExecutorService executor = Executors.newSingleThreadExecutor();
    Callable<Void> unpacker = new Callable<Void>() {
        @Override
        public Void call()
        throws IOException {
            try (InputStream file =
                    new GZIPInputStream(
                        new BufferedInputStream(
                            Files.newInputStream(packGzFile)));
                 JarOutputStream jarOutput = new JarOutputStream(pipeOut)) {

                Pack200.newUnpacker().unpack(file, jarOutput);
                return null;
            } finally {
                executor.shutdown();
            }
        }
    };
    executor.submit(unpacker);

    return new JarInputStream(pipeIn);
}

应该就足够了。但是,仍有两个问题:

  • 如果解包操作出错,您将永远不会知道它,因为ExecutorServices会禁止他们的任务ṣ例外。
  • JarInputStream和JarOutputStream(更具体地说,它们的超类,InflaterInputStream和DeflaterOutputStream)不适用于管道。这是因为关闭JarOutputStream会强制调用其finish()方法,但管道另一端的JarInputStream将永远不会读取该数据。这意味着在调用finish()之前几乎肯定会关闭JarInputStream,导致finish()由于管道损坏而生成IOException。

要解决第一个问题,我们可以覆盖JarInputStream的close方法来解决解包操作中的任何失败。要解决第二个问题,我们可以覆盖JarOutputStream中的finish()来执行任何操作:

public JarInputStream readPackFile(Path packGzFile)
throws IOException {

    PipedInputStream pipeIn = new PipedInputStream();
    PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);

    class NonFinishingJarOutputStream
    extends JarOutputStream {
        NonFinishingJarOutputStream(OutputStream out)
        throws IOException {
            super(out);
        }

        @Override
        public void finish()
        throws IOException {
            // Deliberately empty.
        }
    }

    ExecutorService executor = Executors.newSingleThreadExecutor();
    Callable<Void> unpacker = new Callable<Void>() {
        @Override
        public Void call()
        throws IOException {
            try (InputStream file =
                    new GZIPInputStream(
                        new BufferedInputStream(
                            Files.newInputStream(packGzFile)));
                 JarOutputStream jarOutput =
                    new NonFinishingJarOutputStream(pipeOut)) {

                Pack200.newUnpacker().unpack(file, jarOutput);
                return null;
            } finally {
                executor.shutdown();
            }
        }
    };
    Future<?> unpackerTask = executor.submit(unpacker);

    return new JarInputStream(pipeIn) {
        @Override
        public void close()
        throws IOException {
            super.close();
            try {
                // If the unpack generated an exception, propagate it here.
                unpackerTask.get();
            } catch (ExecutionException e) {
                throw new IOException(e);
            } catch (InterruptedException e) {
                InterruptedIOException iie = new InterruptedIOException();
                iie.initCause(e);
                throw iie;
            }
        }
    };
}