仅使用纯 java 从 jar 文件复制目录
Copy directory from a jar file using only pure java
在我的资源文件夹中,我有一个名为 init 的文件夹。我想将该文件夹及其内部的所有内容复制到名为 ready 的文件夹中的 jar 外部。我想在不使用任何外部库的情况下做到这一点,只是纯粹的 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 和 [=41] 的纯 java 解决方案=].
这是一种非常复杂的方法。它还完全取决于您的应用程序是否在 jar 中,这使得测试、部署到运行时模块化加载器等变得棘手。
有更简单的方法可以做到这一点。具体来说,SomeClass.class.getResourceAsStream("/foo/bar")
将为您提供条目 /foo/bar
的 InputStream
在与表示 SomeClass
的 class 文件所在的相同 class 路径根目录中- 无论是 jar 文件、普通的旧目录还是更奇特的东西。
这就是您应该 'copy' 处理文件的方式。这个简单的代码:
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);
}
}
现在您只需要一个要复制的所有文件的列表。 class路径抽象只是不支持列表。所以,任何破解它的尝试都只是:黑客。当你例如时它会失败有模块化的装载机等。您可以这样做 - 如果您的代码在 jar 文件中 而不是 ,那么您已经在编写代码了。编写一个方法为您提供基于 'dir on the file system' 的 class 路径以及基于 'jar file' 的路径的所有内容的列表并不难,但是有一个现成的替代方法:制作文本具有已知名称的文件,该文件在编译时 列出了所有资源。您可以自己编写,也可以编写脚本。可以像 ls src/resources/* > filesToCopy.txt
或诸如此类的东西一样微不足道。您还可以使用注释处理器来生成这样的文件。
一旦你知道文件存在(它与你要复制的文件在同一个 jar 中),用 getResourceAsStream
以同样的方式读取它,现在你有一个资源列表要写出来使用上面的代码。这个技巧意味着您的代码与 ClassLoader 的 API 完全兼容:您只是依赖 'get me an inputstream with the full data of this named resource' 的保证功能,没有别的。
注意@rzwitserloot 的回答和警告,我也不推荐这种方法,因为它不会 运行 在所有情况下 - 仅来自特定的 jar 文件系统,因此不会通过 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"));
在我的资源文件夹中,我有一个名为 init 的文件夹。我想将该文件夹及其内部的所有内容复制到名为 ready 的文件夹中的 jar 外部。我想在不使用任何外部库的情况下做到这一点,只是纯粹的 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 和 [=41] 的纯 java 解决方案=].
这是一种非常复杂的方法。它还完全取决于您的应用程序是否在 jar 中,这使得测试、部署到运行时模块化加载器等变得棘手。
有更简单的方法可以做到这一点。具体来说,SomeClass.class.getResourceAsStream("/foo/bar")
将为您提供条目 /foo/bar
的 InputStream
在与表示 SomeClass
的 class 文件所在的相同 class 路径根目录中- 无论是 jar 文件、普通的旧目录还是更奇特的东西。
这就是您应该 'copy' 处理文件的方式。这个简单的代码:
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);
}
}
现在您只需要一个要复制的所有文件的列表。 class路径抽象只是不支持列表。所以,任何破解它的尝试都只是:黑客。当你例如时它会失败有模块化的装载机等。您可以这样做 - 如果您的代码在 jar 文件中 而不是 ,那么您已经在编写代码了。编写一个方法为您提供基于 'dir on the file system' 的 class 路径以及基于 'jar file' 的路径的所有内容的列表并不难,但是有一个现成的替代方法:制作文本具有已知名称的文件,该文件在编译时 列出了所有资源。您可以自己编写,也可以编写脚本。可以像 ls src/resources/* > filesToCopy.txt
或诸如此类的东西一样微不足道。您还可以使用注释处理器来生成这样的文件。
一旦你知道文件存在(它与你要复制的文件在同一个 jar 中),用 getResourceAsStream
以同样的方式读取它,现在你有一个资源列表要写出来使用上面的代码。这个技巧意味着您的代码与 ClassLoader 的 API 完全兼容:您只是依赖 'get me an inputstream with the full data of this named resource' 的保证功能,没有别的。
注意@rzwitserloot 的回答和警告,我也不推荐这种方法,因为它不会 运行 在所有情况下 - 仅来自特定的 jar 文件系统,因此不会通过 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"));