Java 从 jar 文件复制目录

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1386809/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 11:30:10  来源:igfitidea点击:

Copy directory from a jar file

javajargetresource

提问by Macarse

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

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

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.

在写这篇文章时,我正在考虑而不是在资源路径中有一个模板目录有一个 zip 文件。通过这种方式,我可以将文件作为 inputStream 获取并将其解压缩到我需要的位置。但我不确定这是否是正确的方法。

采纳答案by Vinay Sajip

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

我认为您使用 zip 文件的方法是有道理的。据推测,您将执行 agetResourceAsStream来了解 zip 的内部结构,它在逻辑上看起来像一个目录树。

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 
}

回答by Vineet Reynolds

You could use the ClassLoaderto 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.

您可以使用ClassLoader获取资源。获得 InputStream 后,您可以读取流的内容并将其写入 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.

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

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

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

回答by ChssPly76

I'm not sure what FileHelperis 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):

我不确定是什么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();

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)

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

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

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

回答by nivekastoreth

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

我讨厌使用之前发布的 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));
            }
        }
    }
}

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

使用示例(将类路径资源“config”中的所有文件复制到“${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.

这应该适用于从平面文件和 Jar 文件复制。

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.

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

回答by jabber

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

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

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

/我为这个解决方案添加了额外的信息,检查代码的末尾,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 :

它仅使用来自 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;
  }

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.

我对 Apache 许可证的了解有限,但您可以在没有库的情况下在代码中使用此方法。但是,如果有许可证问题,我不负责。

回答by lpiepiora

Using Java7+ this can be achieved by creating FileSystemand then using walkFileTreeto copy files recursively.

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

    });
}

The method can be used like this:

该方法可以这样使用:

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

回答by 4F2E4A2E

Here is a working version from the tess4jproject:

这是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();
    }

}

回答by Duc Loi

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 objectThe new code of copyFromJar should like:

lpiepiora 的回答,是正确的!但是有个小问题,源码,应该是jar Url。当源路径是文件系统的路径时,上面的代码将无法正常工作。为了解决这个问题,你应该使用ReferencePath,代码,你可以从以下链接中获取: Read from file system via FileSystem objectcopyFromJar 的新代码应该是这样的:

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

回答by galvanom

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

我最近遇到了类似的问题。我试图从java资源中提取文件夹。所以我用 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);
    }
}

}

}

回答by Miguel Jiménez

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, copyResourceDirectoryand jar.

我知道这个问题现在有点老了,但是在尝试了一些不起作用的答案以及其他只需要一个方法就需要整个库的答案之后,我决定组合一个类。它不需要第三方库,它已经与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();
    }

}