【问题标题】:Copy directory from a jar file using only pure java仅使用纯 java 从 jar 文件中复制目录
【发布时间】:2022-01-20 20:29:24
【问题描述】:

在我的资源文件夹中,我有一个名为 init 的文件夹。我想将该文件夹和其中的所有内容复制到 jar 外部名为 ready 的文件夹中。我想在不使用任何外部库的情况下做到这一点,只使用纯 java。

我已经尝试了以下

public static void copyFromJar(String source, final Path target)
throws
URISyntaxException,
IOException
{
    URI        resource   = ServerInitializer.class.getResource("").toURI();
    FileSystem fileSystem = FileSystems.newFileSystem(resource, Collections.<String, String>emptyMap());

    final Path jarPath = fileSystem.getPath(source);

    Files.walkFileTree(jarPath, new SimpleFileVisitor<>()
    {
        private Path currentTarget;

        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
        throws
        IOException
        {
            currentTarget = target.resolve(jarPath.relativize(dir).toString());
            Files.createDirectories(currentTarget);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
        throws
        IOException
        {
            Files.copy(file, target.resolve(jarPath.relativize(file).toString()),
                       StandardCopyOption.REPLACE_EXISTING);
            return FileVisitResult.CONTINUE;
        }
    });
}

但是我的应用程序已经挂了

FileSystem fileSystem = FileSystems.newFileSystem(resource, Collections.<String, String>emptyMap());

例外

java.lang.IllegalArgumentException: Path component should be '/'

当我打电话时

copyFromJar("/init", Paths.get("ready");

知道我做错了什么吗?或者有人可以提供代码来将目录从 jar 复制到外部而不使用任何外部库吗?

仅供参考,我已经查看了this 解决方案,但它太旧并且使用 apache 库,但我需要适用于 windows 和 linux 的纯 java 解决方案。

【问题讨论】:

    标签: java jar java-11 getresource


    【解决方案1】:

    这是一种非常复杂的方法。它还完全依赖于您的应用程序是否在一个 jar 中,这使得测试、部署到运行时模块化加载器等变得很棘手。

    有很多更简单的方法可以做到这一点。具体来说,SomeClass.class.getResourceAsStream("/foo/bar") 将在与代表SomeClass 的类文件所在的类文件所在的相同类路径根目录中为条目/foo/bar 提供InputStream,无论它是一个jar 文件、一个普通的旧目录还是更奇特的东西。

    这就是您应该“复制”文件的方式。这个简单的代码:

    String theResource = "com/foo/quillionsapp/toCopy/example.txt";
    Path targetPath = ....;
    
    try (var in = YourClass.class.getResourceAsStream("/" + theResource)) {
      Path tgt = targetPath.resolve(theResource);
      Files.createDirectories(tgt.getParent());
      try (var out = Files.newOutputStream(tgt)) {
        in.transferTo(out);
      }
    }
    

    现在您只需要一个要复制的所有文件的列表。类路径抽象只是不支持列表。所以,任何试图破解它的尝试都是这样的:黑客。当你例如它会失败有模块化装载机等。您可以这样做 - 如果您的代码 not 在 jar 文件中,那么您已经在编写代码。编写一个方法可以为您提供基于“文件系统上的目录”的类路径以及基于“jar 文件”的类路径的所有内容列表的方法并不难,但有一个现成的替代方案:使用已知的在编译时列出所有资源的名称。您可以自己编写,也可以编写脚本。可以像ls src/resources/* &gt; filesToCopy.txt 或诸如此类的微不足道。您还可以使用注释处理器来生成这样的文件。

    一旦你知道文件存在(它和你要复制的文件在 jar 中),用 getResourceAsStream 以同样的方式读取它,现在你有一个资源列表可以使用上面的代码写出.这个技巧意味着您的代码与 ClassLoader 的 API 完全兼容:您只依赖于“为我提供包含此命名资源的完整数据的输入流”的保证功能,仅此而已。

    【讨论】:

    • 甚至可以进一步简化。而不是try (var out = Files.newOutputStream(tgt)) { in.transferTo(out); },您可以简单地使用Files.copy(in, tgt);
    • 附带说明,文件系统抽象与模块配合得非常好,甚至比 jar 文件更好,因为您不必创建新的文件系统。模块的文件系统总是像默认文件系统一样打开,所以例如你可以简单地做System.out.println("Copied Object.class to " + Files.copy(Paths.get(Object.class.getResource("Object.class").toURI()), Files.createTempFile("Object", ".class"), StandardCopyOption.REPLACE_EXISTING));this answer 讨论如何处理不同的场景。
    【解决方案2】:

    注意@rzwitserloot 的回答和警告,我也不推荐这种方法,因为它不会在所有情况下运行 - 只能从特定的 jars 中运行,而不是爆炸的文件系统,因此不能通过 IDE 调试器工作,并且可能无法工作未来。

    话虽如此,您所做的只是从 ZIP 文件系统解压缩。这需要一个简单的辅助方法来copy 任何要定位的资源:

    static boolean copy(Path from, BasicFileAttributes a, Path target) {
        System.out.println("Copy "+(a.isDirectory() ? "DIR " : "FILE")+" => "+target);
        try {
            if (a.isDirectory())
                Files.createDirectories(target);
            else if (a.isRegularFile())
                Files.copy(from, target, StandardCopyOption.REPLACE_EXISTING);
        }
        catch (IOException e) {
            throw new UncheckedIOException(e);
        }
        return true;
    }
    

    unzip 的区别在于从类加载器而不是Path 创建文件系统。

    static void copyDir(ClassLoader classLoader, String resPath, Path target) throws IOException, URISyntaxException {
        System.out.println("copyDir("+resPath+", "+target+")");
    
        URI uri = classLoader.getResource(resPath).toURI();
    
        BiPredicate<Path, BasicFileAttributes> foreach = (p,a) -> copy(p,a, Path.of(target.toString(), p.toString())) && false;
    
        try(var fs = FileSystems.newFileSystem(uri, Map.of())) {
            final Path subdir = fs.getPath(resPath);
            for (Path root : fs.getRootDirectories()) {
                System.out.println("root: "+root);
                try (Stream<Path> stream = Files.find(subdir, Integer.MAX_VALUE, foreach)) {
                    stream.count();
                }
            }
        }
    }
    

    然后您可以使用合适的类加载器和路径进行调用:

    copyDir(yourapp.getClass().getClassLoader(), "some/path/in/jar", Path.of("copied.resource.dir"));
    

    【讨论】:

      猜你喜欢
      • 2010-11-26
      • 2011-09-10
      • 2013-07-04
      • 1970-01-01
      • 2012-11-22
      • 1970-01-01
      • 2018-01-10
      • 2017-02-04
      相关资源
      最近更新 更多