从 jar 文件复制目录

发布于 2024-08-03 22:54:23 字数 818 浏览 2 评论 0原文

我最近开发了一个应用程序并创建了 jar 文件。

我的一个类创建一个输出目录,用其资源中的文件填充它。

我的代码是这样的:

// Copy files from dir "template" in this class resource to output.
private void createOutput(File output) throws IOException {

    File template = new File(FileHelper.URL2Path(getClass().getResource("template")));
    FileHelper.copyDirectory(template, output);
}

不幸的是,这不起作用。

我尝试了以下方法,但没有成功:

在编写本文时,我正在考虑不要在资源路径中包含一个带有 zip 文件的模板目录。通过这种方式,我可以将文件作为输入流并将其解压缩到我需要的位置。但我不确定这是否是正确的方法。

I have recently developed an application and created the jar file.

One of my classes creates an output directory, populating it with files from its resource.

My code is something like this:

// Copy files from dir "template" in this class resource to output.
private void createOutput(File output) throws IOException {

    File template = new File(FileHelper.URL2Path(getClass().getResource("template")));
    FileHelper.copyDirectory(template, output);
}

Unfortunately this doesn't work.

I tried the following without luck:

While writing this I was thinking about instead of having a template dir in the resource path having a zip file of it. Doing it this way I could get the file as an inputStream and unzip it where I need to. But I am not sure if it's the correct way.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

清眉祭 2024-08-10 22:54:24

感谢您的解决方案!对于其他人,以下不使用辅助类(StringUtils 除外)

/我为此解决方案添加了额外信息,请检查代码末尾,Zegor V/

public class FileUtils {
  public static boolean copyFile(final File toCopy, final File destFile) {
    try {
      return FileUtils.copyStream(new FileInputStream(toCopy),
          new FileOutputStream(destFile));
    } catch (final FileNotFoundException e) {
      e.printStackTrace();
    }
    return false;
  }

  private static boolean copyFilesRecusively(final File toCopy,
      final File destDir) {
    assert destDir.isDirectory();

    if (!toCopy.isDirectory()) {
      return FileUtils.copyFile(toCopy, new File(destDir, toCopy.getName()));
    } else {
      final File newDestDir = new File(destDir, toCopy.getName());
      if (!newDestDir.exists() && !newDestDir.mkdir()) {
        return false;
      }
      for (final File child : toCopy.listFiles()) {
        if (!FileUtils.copyFilesRecusively(child, newDestDir)) {
          return false;
        }
      }
    }
    return true;
  }

  public static boolean copyJarResourcesRecursively(final File destDir,
      final JarURLConnection jarConnection) throws IOException {

    final JarFile jarFile = jarConnection.getJarFile();

    for (final Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {
      final JarEntry entry = e.nextElement();
      if (entry.getName().startsWith(jarConnection.getEntryName())) {
        final String filename = StringUtils.removeStart(entry.getName(), //
            jarConnection.getEntryName());

        final File f = new File(destDir, filename);
        if (!entry.isDirectory()) {
          final InputStream entryInputStream = jarFile.getInputStream(entry);
          if(!FileUtils.copyStream(entryInputStream, f)){
            return false;
          }
          entryInputStream.close();
        } else {
          if (!FileUtils.ensureDirectoryExists(f)) {
            throw new IOException("Could not create directory: "
                + f.getAbsolutePath());
          }
        }
      }
    }
    return true;
  }

  public static boolean copyResourcesRecursively( //
      final URL originUrl, final File destination) {
    try {
      final URLConnection urlConnection = originUrl.openConnection();
      if (urlConnection instanceof JarURLConnection) {
        return FileUtils.copyJarResourcesRecursively(destination,
            (JarURLConnection) urlConnection);
      } else {
        return FileUtils.copyFilesRecusively(new File(originUrl.getPath()),
            destination);
      }
    } catch (final IOException e) {
      e.printStackTrace();
    }
    return false;
  }

  private static boolean copyStream(final InputStream is, final File f) {
    try {
      return FileUtils.copyStream(is, new FileOutputStream(f));
    } catch (final FileNotFoundException e) {
      e.printStackTrace();
    }
    return false;
  }

  private static boolean copyStream(final InputStream is, final OutputStream os) {
    try {
      final byte[] buf = new byte[1024];

      int len = 0;
      while ((len = is.read(buf)) > 0) {
        os.write(buf, 0, len);
      }
      is.close();
      os.close();
      return true;
    } catch (final IOException e) {
      e.printStackTrace();
    }
    return false;
  }

  private static boolean ensureDirectoryExists(final File f) {
    return f.exists() || f.mkdir();
  }
}

它只使用 Apache Software Foundation 的一个外部库,但使用的功能仅:

  public static String removeStart(String str, String remove) {
      if (isEmpty(str) || isEmpty(remove)) {
          return str;
      }
      if (str.startsWith(remove)){
          return str.substring(remove.length());
      }
      return str;
  }
  public static boolean isEmpty(CharSequence cs) {
      return cs == null || cs.length() == 0;
  }

我的知识仅限于 Apache 许可证,但您可以在没有库的情况下在代码中使用此方法。但是,如果有许可证问题,我不承担任何责任。

Thanks for the solution! For others, the following doesn't make use of the auxiliary classes (except for StringUtils)

/I added extra information for this solution, check the end of the code, Zegor V/

public class FileUtils {
  public static boolean copyFile(final File toCopy, final File destFile) {
    try {
      return FileUtils.copyStream(new FileInputStream(toCopy),
          new FileOutputStream(destFile));
    } catch (final FileNotFoundException e) {
      e.printStackTrace();
    }
    return false;
  }

  private static boolean copyFilesRecusively(final File toCopy,
      final File destDir) {
    assert destDir.isDirectory();

    if (!toCopy.isDirectory()) {
      return FileUtils.copyFile(toCopy, new File(destDir, toCopy.getName()));
    } else {
      final File newDestDir = new File(destDir, toCopy.getName());
      if (!newDestDir.exists() && !newDestDir.mkdir()) {
        return false;
      }
      for (final File child : toCopy.listFiles()) {
        if (!FileUtils.copyFilesRecusively(child, newDestDir)) {
          return false;
        }
      }
    }
    return true;
  }

  public static boolean copyJarResourcesRecursively(final File destDir,
      final JarURLConnection jarConnection) throws IOException {

    final JarFile jarFile = jarConnection.getJarFile();

    for (final Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {
      final JarEntry entry = e.nextElement();
      if (entry.getName().startsWith(jarConnection.getEntryName())) {
        final String filename = StringUtils.removeStart(entry.getName(), //
            jarConnection.getEntryName());

        final File f = new File(destDir, filename);
        if (!entry.isDirectory()) {
          final InputStream entryInputStream = jarFile.getInputStream(entry);
          if(!FileUtils.copyStream(entryInputStream, f)){
            return false;
          }
          entryInputStream.close();
        } else {
          if (!FileUtils.ensureDirectoryExists(f)) {
            throw new IOException("Could not create directory: "
                + f.getAbsolutePath());
          }
        }
      }
    }
    return true;
  }

  public static boolean copyResourcesRecursively( //
      final URL originUrl, final File destination) {
    try {
      final URLConnection urlConnection = originUrl.openConnection();
      if (urlConnection instanceof JarURLConnection) {
        return FileUtils.copyJarResourcesRecursively(destination,
            (JarURLConnection) urlConnection);
      } else {
        return FileUtils.copyFilesRecusively(new File(originUrl.getPath()),
            destination);
      }
    } catch (final IOException e) {
      e.printStackTrace();
    }
    return false;
  }

  private static boolean copyStream(final InputStream is, final File f) {
    try {
      return FileUtils.copyStream(is, new FileOutputStream(f));
    } catch (final FileNotFoundException e) {
      e.printStackTrace();
    }
    return false;
  }

  private static boolean copyStream(final InputStream is, final OutputStream os) {
    try {
      final byte[] buf = new byte[1024];

      int len = 0;
      while ((len = is.read(buf)) > 0) {
        os.write(buf, 0, len);
      }
      is.close();
      os.close();
      return true;
    } catch (final IOException e) {
      e.printStackTrace();
    }
    return false;
  }

  private static boolean ensureDirectoryExists(final File f) {
    return f.exists() || f.mkdir();
  }
}

It uses only one external library from the Apache Software Foundation, however the used functions are only :

  public static String removeStart(String str, String remove) {
      if (isEmpty(str) || isEmpty(remove)) {
          return str;
      }
      if (str.startsWith(remove)){
          return str.substring(remove.length());
      }
      return str;
  }
  public static boolean isEmpty(CharSequence cs) {
      return cs == null || cs.length() == 0;
  }

My knowledge is limited on Apache licence, but you can use this methods in your code without library. However, i am not responsible for licence issues, if there is.

朮生 2024-08-10 22:54:24

使用 Java7+ 可以通过创建 FileSystem 然后使用 walkFileTree 递归复制文件来实现。

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


    final Path jarPath = fileSystem.getPath(source);

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

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

    });
}

该方法可以这样使用:

copyFromJar("/path/to/the/template/in/jar", Paths.get("/tmp/from-jar"))

Using Java7+ this can be achieved by creating FileSystem and then using walkFileTree to copy files recursively.

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


    final Path jarPath = fileSystem.getPath(source);

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

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

    });
}

The method can be used like this:

copyFromJar("/path/to/the/template/in/jar", Paths.get("/tmp/from-jar"))
始终不够爱げ你 2024-08-10 22:54:24

我认为您使用 zip 文件的方法是有意义的。大概您将执行 getResourceAsStream 来获取 zip 的内部结构,它在逻辑上看起来像目录树。

骨架方法:

InputStream is = getClass().getResourceAsStream("my_embedded_file.zip");
ZipInputStream zis = new ZipInputStream(is);
ZipEntry entry;

while ((entry = zis.getNextEntry()) != null) {
    // do something with the entry - for example, extract the data 
}

I think your approach of using a zip file makes sense. Presumably you'll do a getResourceAsStream to get at the internals of the zip, which will logically look like a directory tree.

A skeleton approach:

InputStream is = getClass().getResourceAsStream("my_embedded_file.zip");
ZipInputStream zis = new ZipInputStream(is);
ZipEntry entry;

while ((entry = zis.getNextEntry()) != null) {
    // do something with the entry - for example, extract the data 
}
2024-08-10 22:54:24

我讨厌使用之前发布的 ZIP 文件方法的想法,所以我想出了以下方法。

public void copyResourcesRecursively(URL originUrl, File destination) throws Exception {
    URLConnection urlConnection = originUrl.openConnection();
    if (urlConnection instanceof JarURLConnection) {
        copyJarResourcesRecursively(destination, (JarURLConnection) urlConnection);
    } else if (urlConnection instanceof FileURLConnection) {
        FileUtils.copyFilesRecursively(new File(originUrl.getPath()), destination);
    } else {
        throw new Exception("URLConnection[" + urlConnection.getClass().getSimpleName() +
                "] is not a recognized/implemented connection type.");
    }
}

public void copyJarResourcesRecursively(File destination, JarURLConnection jarConnection ) throws IOException {
    JarFile jarFile = jarConnection.getJarFile();
    for (JarEntry entry : CollectionUtils.iterable(jarFile.entries())) {
        if (entry.getName().startsWith(jarConnection.getEntryName())) {
            String fileName = StringUtils.removeStart(entry.getName(), jarConnection.getEntryName());
            if (!entry.isDirectory()) {
                InputStream entryInputStream = null;
                try {
                    entryInputStream = jarFile.getInputStream(entry);
                    FileUtils.copyStream(entryInputStream, new File(destination, fileName));
                } finally {
                    FileUtils.safeClose(entryInputStream);
                }
            } else {
                FileUtils.ensureDirectoryExists(new File(destination, fileName));
            }
        }
    }
}

示例用法(将所有文件从类路径资源“config”复制到“${homeDirectory}/config”:

File configHome = new File(homeDirectory, "config/");
//noinspection ResultOfMethodCallIgnored
configHome.mkdirs();
copyResourcesRecursively(super.getClass().getResource("/config"), configHome);

这对于从平面文件和 Jar 文件进行复制都应该有效。

注意:上面的代码使用了一些自定义实用程序类( FileUtils、CollectionUtils)以及一些来自 Apache commons-lang (StringUtils) 的函数,但这些函数的命名应该相当明显。

I hated the idea of using the ZIP file method posted earlier, so I came up with the following.

public void copyResourcesRecursively(URL originUrl, File destination) throws Exception {
    URLConnection urlConnection = originUrl.openConnection();
    if (urlConnection instanceof JarURLConnection) {
        copyJarResourcesRecursively(destination, (JarURLConnection) urlConnection);
    } else if (urlConnection instanceof FileURLConnection) {
        FileUtils.copyFilesRecursively(new File(originUrl.getPath()), destination);
    } else {
        throw new Exception("URLConnection[" + urlConnection.getClass().getSimpleName() +
                "] is not a recognized/implemented connection type.");
    }
}

public void copyJarResourcesRecursively(File destination, JarURLConnection jarConnection ) throws IOException {
    JarFile jarFile = jarConnection.getJarFile();
    for (JarEntry entry : CollectionUtils.iterable(jarFile.entries())) {
        if (entry.getName().startsWith(jarConnection.getEntryName())) {
            String fileName = StringUtils.removeStart(entry.getName(), jarConnection.getEntryName());
            if (!entry.isDirectory()) {
                InputStream entryInputStream = null;
                try {
                    entryInputStream = jarFile.getInputStream(entry);
                    FileUtils.copyStream(entryInputStream, new File(destination, fileName));
                } finally {
                    FileUtils.safeClose(entryInputStream);
                }
            } else {
                FileUtils.ensureDirectoryExists(new File(destination, fileName));
            }
        }
    }
}

Example Useage (copies all files from the classpath resource "config" to "${homeDirectory}/config":

File configHome = new File(homeDirectory, "config/");
//noinspection ResultOfMethodCallIgnored
configHome.mkdirs();
copyResourcesRecursively(super.getClass().getResource("/config"), configHome);

This should work both for copying from both flat files as well as Jar files.

Note: The code above uses some custom utility classes (FileUtils, CollectionUtils) as well as some from Apache commons-lang (StringUtils), but the functions should be named fairly obviously.

┾廆蒐ゝ 2024-08-10 22:54:24

lpiepiora的答案是正确的!但有一个小问题,
源,应该是一个jar URL。当源路径是文件系统的路径时,上述代码将无法正常工作。
要解决这个问题,应该使用ReferencePath,代码可以从以下链接获取:
通过 FileSystem 对象从文件系统读取
copyFromJar 的新代码应如下所示:

public class ResourcesUtils {
public static void copyFromJar(final String sourcePath, final Path target) throws URISyntaxException,
        IOException {
    final PathReference pathReference = PathReference.getPath(new URI(sourcePath));
    final Path jarPath = pathReference.getPath();

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

        private Path currentTarget;

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

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

    });
}

public static void main(final String[] args) throws MalformedURLException, URISyntaxException, IOException {
    final String sourcePath = "jar:file:/c:/temp/example.jar!/src/main/resources";
    ResourcesUtils.copyFromJar(sourcePath, Paths.get("c:/temp/resources"));
}

The answer of lpiepiora, is correct! But there is a minor issue,
The source, should be a jar Url. When the source path is path to a file system, then the above code will not work proper.
To solve this problem, you should use the ReferencePath, the code, you can get from the following link:
Read from file system via FileSystem object
The new code of copyFromJar should like:

public class ResourcesUtils {
public static void copyFromJar(final String sourcePath, final Path target) throws URISyntaxException,
        IOException {
    final PathReference pathReference = PathReference.getPath(new URI(sourcePath));
    final Path jarPath = pathReference.getPath();

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

        private Path currentTarget;

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

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

    });
}

public static void main(final String[] args) throws MalformedURLException, URISyntaxException, IOException {
    final String sourcePath = "jar:file:/c:/temp/example.jar!/src/main/resources";
    ResourcesUtils.copyFromJar(sourcePath, Paths.get("c:/temp/resources"));
}
束缚m 2024-08-10 22:54:24

我不确定 FileHelper 是什么或做什么,但您将无法直接从 JAR 复制文件(或目录)。正如您所提到的,使用 InputStream 是正确的方法(来自 jar 或 zip):

InputStream is = getClass().getResourceAsStream("file_in_jar");
OutputStream os = new FileOutputStream("dest_file");
byte[] buffer = new byte[4096];
int length;
while ((length = is.read(buffer)) > 0) {
    os.write(buffer, 0, length);
}
os.close();
is.close();

您需要对每个文件执行上述操作(当然,适当地处理异常)。您可能能够也可能无法(取决于您的部署配置)将有问题的 jar 文件读取为 JarFile(例如,如果部署为非扩展 Web 应用程序的一部分,则它可能无法作为实际文件使用)。如果您能阅读它,您应该能够迭代 JarEntry 实例列表,从而重建您的目录结构;否则您可能需要将其存储在其他地方(例如,在文本或 xml 资源中)

您可能需要查看 Commons IO 库 - 它提供了许多常用的流/文件功能,包括复制。

I'm not sure what FileHelper is or does, but you will NOT be able to copy files (or directories) directly from JAR. Using InputStream as you've mentioned is the correct way (from either jar or zip):

InputStream is = getClass().getResourceAsStream("file_in_jar");
OutputStream os = new FileOutputStream("dest_file");
byte[] buffer = new byte[4096];
int length;
while ((length = is.read(buffer)) > 0) {
    os.write(buffer, 0, length);
}
os.close();
is.close();

You'll need to do the above (handling exceptions appropriately, of course) for each of your files. You may or may not be able (depending on your deployment configuration) to read jar file in question as JarFile (it may not be available as an actual file if deployed as part of non-expanded web app, for example). If you can read it, you should be able to iterate through list of JarEntry instances and thus reconstitute your directory structure; otherwise you may need to store it elsewhere (within text or xml resource, for example)

You may want to take a look at Commons IO library - it provides a lot of commonly used stream / file functionality including copying.

鹿港巷口少年归 2024-08-10 22:54:24

这是来自 tess4j 项目:

 /**
 * This method will copy resources from the jar file of the current thread and extract it to the destination folder.
 * 
 * @param jarConnection
 * @param destDir
 * @throws IOException
 */
public void copyJarResourceToFolder(JarURLConnection jarConnection, File destDir) {

    try {
        JarFile jarFile = jarConnection.getJarFile();

        /**
         * Iterate all entries in the jar file.
         */
        for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {

            JarEntry jarEntry = e.nextElement();
            String jarEntryName = jarEntry.getName();
            String jarConnectionEntryName = jarConnection.getEntryName();

            /**
             * Extract files only if they match the path.
             */
            if (jarEntryName.startsWith(jarConnectionEntryName)) {

                String filename = jarEntryName.startsWith(jarConnectionEntryName) ? jarEntryName.substring(jarConnectionEntryName.length()) : jarEntryName;
                File currentFile = new File(destDir, filename);

                if (jarEntry.isDirectory()) {
                    currentFile.mkdirs();
                } else {
                    InputStream is = jarFile.getInputStream(jarEntry);
                    OutputStream out = FileUtils.openOutputStream(currentFile);
                    IOUtils.copy(is, out);
                    is.close();
                    out.close();
                }
            }
        }
    } catch (IOException e) {
        // TODO add logger
        e.printStackTrace();
    }

}

Here is a working version from the tess4j project:

 /**
 * This method will copy resources from the jar file of the current thread and extract it to the destination folder.
 * 
 * @param jarConnection
 * @param destDir
 * @throws IOException
 */
public void copyJarResourceToFolder(JarURLConnection jarConnection, File destDir) {

    try {
        JarFile jarFile = jarConnection.getJarFile();

        /**
         * Iterate all entries in the jar file.
         */
        for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {

            JarEntry jarEntry = e.nextElement();
            String jarEntryName = jarEntry.getName();
            String jarConnectionEntryName = jarConnection.getEntryName();

            /**
             * Extract files only if they match the path.
             */
            if (jarEntryName.startsWith(jarConnectionEntryName)) {

                String filename = jarEntryName.startsWith(jarConnectionEntryName) ? jarEntryName.substring(jarConnectionEntryName.length()) : jarEntryName;
                File currentFile = new File(destDir, filename);

                if (jarEntry.isDirectory()) {
                    currentFile.mkdirs();
                } else {
                    InputStream is = jarFile.getInputStream(jarEntry);
                    OutputStream out = FileUtils.openOutputStream(currentFile);
                    IOUtils.copy(is, out);
                    is.close();
                    out.close();
                }
            }
        }
    } catch (IOException e) {
        // TODO add logger
        e.printStackTrace();
    }

}
你的他你的她 2024-08-10 22:54:24

我知道这个问题现在有点老了,但是在尝试了一些不起作用的答案以及其他仅需要整个库才能实现一种方法的答案之后,我决定组建一个课程。它不需要第三方库,并且已经使用 Java 8 进行了测试。有四个公共方法:copyResourcesToTempDircopyResourcesToDircopyResourceDirectoryjar

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.Enumeration;
import java.util.Optional;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * A helper to copy resources from a JAR file into a directory.
 */
public final class ResourceCopy {

    /**
     * URI prefix for JAR files.
     */
    private static final String JAR_URI_PREFIX = "jar:file:";

    /**
     * The default buffer size.
     */
    private static final int BUFFER_SIZE = 8 * 1024;

    /**
     * Copies a set of resources into a temporal directory, optionally preserving
     * the paths of the resources.
     * @param preserve Whether the files should be placed directly in the
     *  directory or the source path should be kept
     * @param paths The paths to the resources
     * @return The temporal directory
     * @throws IOException If there is an I/O error
     */
    public File copyResourcesToTempDir(final boolean preserve,
        final String... paths)
        throws IOException {
        final File parent = new File(System.getProperty("java.io.tmpdir"));
        File directory;
        do {
            directory = new File(parent, String.valueOf(System.nanoTime()));
        } while (!directory.mkdir());
        return this.copyResourcesToDir(directory, preserve, paths);
    }

    /**
     * Copies a set of resources into a directory, preserving the paths
     * and names of the resources.
     * @param directory The target directory
     * @param preserve Whether the files should be placed directly in the
     *  directory or the source path should be kept
     * @param paths The paths to the resources
     * @return The temporal directory
     * @throws IOException If there is an I/O error
     */
    public File copyResourcesToDir(final File directory, final boolean preserve,
        final String... paths) throws IOException {
        for (final String path : paths) {
            final File target;
            if (preserve) {
                target = new File(directory, path);
                target.getParentFile().mkdirs();
            } else {
                target = new File(directory, new File(path).getName());
            }
            this.writeToFile(
                Thread.currentThread()
                    .getContextClassLoader()
                    .getResourceAsStream(path),
                target
            );
        }
        return directory;
    }

    /**
     * Copies a resource directory from inside a JAR file to a target directory.
     * @param source The JAR file
     * @param path The path to the directory inside the JAR file
     * @param target The target directory
     * @throws IOException If there is an I/O error
     */
    public void copyResourceDirectory(final JarFile source, final String path,
        final File target) throws IOException {
        final Enumeration<JarEntry> entries = source.entries();
        final String newpath = String.format("%s/", path);
        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            if (entry.getName().startsWith(newpath) && !entry.isDirectory()) {
                final File dest =
                    new File(target, entry.getName().substring(newpath.length()));
                final File parent = dest.getParentFile();
                if (parent != null) {
                    parent.mkdirs();
                }
                this.writeToFile(source.getInputStream(entry), dest);
            }
        }
    }

    /**
     * The JAR file containing the given class.
     * @param clazz The class
     * @return The JAR file or null
     * @throws IOException If there is an I/O error
     */
    public Optional<JarFile> jar(final Class<?> clazz) throws IOException {
        final String path =
            String.format("/%s.class", clazz.getName().replace('.', '/'));
        final URL url = clazz.getResource(path);
        Optional<JarFile> optional = Optional.empty();
        if (url != null) {
            final String jar = url.toString();
            final int bang = jar.indexOf('!');
            if (jar.startsWith(ResourceCopy.JAR_URI_PREFIX) && bang != -1) {
                optional = Optional.of(
                    new JarFile(
                        jar.substring(ResourceCopy.JAR_URI_PREFIX.length(), bang)
                    )
                );
            }
        }
        return optional;
    }

    /**
     * Writes an input stream to a file.
     * @param input The input stream
     * @param target The target file
     * @throws IOException If there is an I/O error
     */
    private void writeToFile(final InputStream input, final File target)
        throws IOException {
        final OutputStream output = Files.newOutputStream(target.toPath());
        final byte[] buffer = new byte[ResourceCopy.BUFFER_SIZE];
        int length = input.read(buffer);
        while (length > 0) {
            output.write(buffer, 0, length);
            length = input.read(buffer);
        }
        input.close();
        output.close();
    }

}

I know this question is kind of old now but after trying some answers that didn't work and others that required a whole library for just one method, I decided to put together a class. It doesn't require third-party libraries and it's been tested with Java 8. There are four public methods: copyResourcesToTempDir, copyResourcesToDir, copyResourceDirectory and jar.

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.Enumeration;
import java.util.Optional;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * A helper to copy resources from a JAR file into a directory.
 */
public final class ResourceCopy {

    /**
     * URI prefix for JAR files.
     */
    private static final String JAR_URI_PREFIX = "jar:file:";

    /**
     * The default buffer size.
     */
    private static final int BUFFER_SIZE = 8 * 1024;

    /**
     * Copies a set of resources into a temporal directory, optionally preserving
     * the paths of the resources.
     * @param preserve Whether the files should be placed directly in the
     *  directory or the source path should be kept
     * @param paths The paths to the resources
     * @return The temporal directory
     * @throws IOException If there is an I/O error
     */
    public File copyResourcesToTempDir(final boolean preserve,
        final String... paths)
        throws IOException {
        final File parent = new File(System.getProperty("java.io.tmpdir"));
        File directory;
        do {
            directory = new File(parent, String.valueOf(System.nanoTime()));
        } while (!directory.mkdir());
        return this.copyResourcesToDir(directory, preserve, paths);
    }

    /**
     * Copies a set of resources into a directory, preserving the paths
     * and names of the resources.
     * @param directory The target directory
     * @param preserve Whether the files should be placed directly in the
     *  directory or the source path should be kept
     * @param paths The paths to the resources
     * @return The temporal directory
     * @throws IOException If there is an I/O error
     */
    public File copyResourcesToDir(final File directory, final boolean preserve,
        final String... paths) throws IOException {
        for (final String path : paths) {
            final File target;
            if (preserve) {
                target = new File(directory, path);
                target.getParentFile().mkdirs();
            } else {
                target = new File(directory, new File(path).getName());
            }
            this.writeToFile(
                Thread.currentThread()
                    .getContextClassLoader()
                    .getResourceAsStream(path),
                target
            );
        }
        return directory;
    }

    /**
     * Copies a resource directory from inside a JAR file to a target directory.
     * @param source The JAR file
     * @param path The path to the directory inside the JAR file
     * @param target The target directory
     * @throws IOException If there is an I/O error
     */
    public void copyResourceDirectory(final JarFile source, final String path,
        final File target) throws IOException {
        final Enumeration<JarEntry> entries = source.entries();
        final String newpath = String.format("%s/", path);
        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            if (entry.getName().startsWith(newpath) && !entry.isDirectory()) {
                final File dest =
                    new File(target, entry.getName().substring(newpath.length()));
                final File parent = dest.getParentFile();
                if (parent != null) {
                    parent.mkdirs();
                }
                this.writeToFile(source.getInputStream(entry), dest);
            }
        }
    }

    /**
     * The JAR file containing the given class.
     * @param clazz The class
     * @return The JAR file or null
     * @throws IOException If there is an I/O error
     */
    public Optional<JarFile> jar(final Class<?> clazz) throws IOException {
        final String path =
            String.format("/%s.class", clazz.getName().replace('.', '/'));
        final URL url = clazz.getResource(path);
        Optional<JarFile> optional = Optional.empty();
        if (url != null) {
            final String jar = url.toString();
            final int bang = jar.indexOf('!');
            if (jar.startsWith(ResourceCopy.JAR_URI_PREFIX) && bang != -1) {
                optional = Optional.of(
                    new JarFile(
                        jar.substring(ResourceCopy.JAR_URI_PREFIX.length(), bang)
                    )
                );
            }
        }
        return optional;
    }

    /**
     * Writes an input stream to a file.
     * @param input The input stream
     * @param target The target file
     * @throws IOException If there is an I/O error
     */
    private void writeToFile(final InputStream input, final File target)
        throws IOException {
        final OutputStream output = Files.newOutputStream(target.toPath());
        final byte[] buffer = new byte[ResourceCopy.BUFFER_SIZE];
        int length = input.read(buffer);
        while (length > 0) {
            output.write(buffer, 0, length);
            length = input.read(buffer);
        }
        input.close();
        output.close();
    }

}
余生再见 2024-08-10 22:54:24

您可以使用 ClassLoader 来获取 流式传输到资源。获得输入流后,您可以读取该流的内容并将其写入输出流。

在您的情况下,您需要创建多个 OutputStream 实例,每个实例对应一个要复制到目标的文件。当然,这需要您事先知道文件名。

对于此任务,最好使用 getResourceAsStream,而不是 getResource 或 getResources()。

You could use the ClassLoader to obtain a stream to the resource. Once you have obtained an InputStream, you can read off, and write the contents of the stream, onto an OutputStream.

In your case, you'll need to create several OutputStream instances, one for each file that you want to copy over to the destination. This of course, requires that you know of the file names before hand.

For this task, it is preferred to use getResourceAsStream, rather than getResource or getResources().

把梦留给海 2024-08-10 22:54:24

我最近也遇到了类似的问题。我尝试从 java 资源中提取文件夹。所以我用 Spring PathMatchingResourcePatternResolver 解决了这个问题。

此代码从指定资源获取所有文件和目录:

        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + resourceFolder + "/**");

这是将所有文件和目录从资源复制到磁盘路径的类。

public class ResourceExtractor {

public static final Logger logger = 
Logger.getLogger(ResourceExtractor.class);

public void extract(String resourceFolder, String destinationFolder){
    try {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + resourceFolder + "/**");
        URI inJarUri  = new DefaultResourceLoader().getResource("classpath:" + resourceFolder).getURI();

        for (Resource resource : resources){
            String relativePath = resource
                        .getURI()
                        .getRawSchemeSpecificPart()
                        .replace(inJarUri.getRawSchemeSpecificPart(), "");
            if (relativePath.isEmpty()){
                continue;
            }
            if (relativePath.endsWith("/") || relativePath.endsWith("\\")) {
                File dirFile = new File(destinationFolder + relativePath);
                if (!dirFile.exists()) {
                    dirFile.mkdir();
                }
            }
            else{
                copyResourceToFilePath(resource, destinationFolder + relativePath);
            }
        }
    }
    catch (IOException e){
        logger.debug("Extraction failed!", e );
    }
}

private void copyResourceToFilePath(Resource resource, String filePath) throws IOException{
    InputStream resourceInputStream = resource.getInputStream();
    File file = new File(filePath);
    if (!file.exists()) {
        FileUtils.copyInputStreamToFile(resourceInputStream, file);
    }
}

}

I have faced the similair problem recently. I tried to extract folder from java resources. So I resolved this issue with Spring PathMatchingResourcePatternResolver.

This code gets all files and directories from the specified resource:

        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + resourceFolder + "/**");

And this is the class that copy all files and directories from the resource to the disk path.

public class ResourceExtractor {

public static final Logger logger = 
Logger.getLogger(ResourceExtractor.class);

public void extract(String resourceFolder, String destinationFolder){
    try {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + resourceFolder + "/**");
        URI inJarUri  = new DefaultResourceLoader().getResource("classpath:" + resourceFolder).getURI();

        for (Resource resource : resources){
            String relativePath = resource
                        .getURI()
                        .getRawSchemeSpecificPart()
                        .replace(inJarUri.getRawSchemeSpecificPart(), "");
            if (relativePath.isEmpty()){
                continue;
            }
            if (relativePath.endsWith("/") || relativePath.endsWith("\\")) {
                File dirFile = new File(destinationFolder + relativePath);
                if (!dirFile.exists()) {
                    dirFile.mkdir();
                }
            }
            else{
                copyResourceToFilePath(resource, destinationFolder + relativePath);
            }
        }
    }
    catch (IOException e){
        logger.debug("Extraction failed!", e );
    }
}

private void copyResourceToFilePath(Resource resource, String filePath) throws IOException{
    InputStream resourceInputStream = resource.getInputStream();
    File file = new File(filePath);
    if (!file.exists()) {
        FileUtils.copyInputStreamToFile(resourceInputStream, file);
    }
}

}

还如梦归 2024-08-10 22:54:24

我喜欢 @nivekastoreth (https://stackoverflow.com/a/2993908/13768174) 的回复,但无法直接使用它,因为它依赖于第三方库。

我只是重写它以使用 java.nio.file.Files

假设我想复制这样的文件夹:

parent
|_folder
  |_item1
  |_item2
|_folder2

我可以像这样调用方法:

final URL configFolderURL = getClass().getResource("in/jar/path/folder");
Path targetDir = Files.createDirectory(Path.of("a","new","dir"));
copyJarResourcesRecursively(targetDir, (JarURLConnection) configFolderURL.openConnection());

这是实现:

private void copyJarResourcesRecursively(Path destination, JarURLConnection jarConnection) throws IOException {
        JarFile jarFile = jarConnection.getJarFile();
        for (Iterator<JarEntry> it = jarFile.entries().asIterator(); it.hasNext();) {
            JarEntry entry = it.next();
            if (entry.getName().startsWith(jarConnection.getEntryName())) {
                if (!entry.isDirectory()) {
                    try (InputStream entryInputStream = jarFile.getInputStream(entry)) {
                        Files.copy(entryInputStream, Paths.get(destination.toString(), entry.getName()));
                    }
                } else {
                    Files.createDirectories(Paths.get(destination.toString(), entry.getName()));
                }
            }
        }
    }

I liked the response from @nivekastoreth (https://stackoverflow.com/a/2993908/13768174) but was not able to use it directly because it was relying on third party libraries.

I just rewrote it to use java.nio.file.Files

Let's say I want to copy a folder like this:

parent
|_folder
  |_item1
  |_item2
|_folder2

I can just call the method like this:

final URL configFolderURL = getClass().getResource("in/jar/path/folder");
Path targetDir = Files.createDirectory(Path.of("a","new","dir"));
copyJarResourcesRecursively(targetDir, (JarURLConnection) configFolderURL.openConnection());

Here is the implementation:

private void copyJarResourcesRecursively(Path destination, JarURLConnection jarConnection) throws IOException {
        JarFile jarFile = jarConnection.getJarFile();
        for (Iterator<JarEntry> it = jarFile.entries().asIterator(); it.hasNext();) {
            JarEntry entry = it.next();
            if (entry.getName().startsWith(jarConnection.getEntryName())) {
                if (!entry.isDirectory()) {
                    try (InputStream entryInputStream = jarFile.getInputStream(entry)) {
                        Files.copy(entryInputStream, Paths.get(destination.toString(), entry.getName()));
                    }
                } else {
                    Files.createDirectories(Paths.get(destination.toString(), entry.getName()));
                }
            }
        }
    }
不必了 2024-08-10 22:54:24

它可以非常容易地使用(apache commons-io)依赖项

import org.apache.commons.io.FileUtils

public void copyResourceFileInsideJarToPath(String resourceName, Path toPath) throws IOException {
    URL fromURL = getClass().getClassLoader().getResource(resourceName);
    LOGGER.info("fromURL: {}", fromURL);
    LOGGER.info("toPath: {}", toPath.toAbsolutePath());

    Files.deleteIfExists(toPath);
    FileUtils.copyURLToFile(fromURL, toPath.toFile());
}

相关的maven依赖项:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

It can very easy using (apache commons-io) dependency

import org.apache.commons.io.FileUtils

public void copyResourceFileInsideJarToPath(String resourceName, Path toPath) throws IOException {
    URL fromURL = getClass().getClassLoader().getResource(resourceName);
    LOGGER.info("fromURL: {}", fromURL);
    LOGGER.info("toPath: {}", toPath.toAbsolutePath());

    Files.deleteIfExists(toPath);
    FileUtils.copyURLToFile(fromURL, toPath.toFile());
}

Related maven dependency:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>
是你 2024-08-10 22:54:24

您可以使用我的图书馆:
编译组:'com.github.ardenliu',名称:'arden-file',版本:'0.0.4'ResourcesUtils

类:
copyFromClassPath(最终字符串资源路径,最终路径targetRoot)

源代码:
https://github.com/ardenliu/common/blob/master/arden-file/src/main/java/com/github/ardenliu/common/file/ResourcesUtils.java

Junit 测试:
Eclipse 类路径的一个测试用例;另一个用于罐子
https://github.com/ardenliu/common/blob/master/arden-file/src/test/java/com/github/ardenliu/common/file/ResourcesUtilsTest.java

You can use my library:
compile group: 'com.github.ardenliu', name: 'arden-file', version: '0.0.4'

ResourcesUtils class:
copyFromClassPath(final String resourcePath, final Path targetRoot)

Source Code:
https://github.com/ardenliu/common/blob/master/arden-file/src/main/java/com/github/ardenliu/common/file/ResourcesUtils.java

Junit testing:
One testing case for Eclipse classpath; another one for jar
https://github.com/ardenliu/common/blob/master/arden-file/src/test/java/com/github/ardenliu/common/file/ResourcesUtilsTest.java

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文