简体   繁体   中英

Copy/extract directory from resources inside Jar file

I am writing an application that needs to copy some static assets from its resources directory. The directory structure is like this;

java/
resources/
    assets/
        file1.txt
        file2.txt
        subdir/
            file3.txt

I want to copy the assets directory (recursively) to a location on the file system. If I use IOUtils.copy(InputStream in, OutputStream out) then I get a null pointer exception.

InputStream in = getClass().getResourceAsStream("/assets");
OutputStream out = new FileOutputStream(new File("/path/to/write/to"));
IOUtils.copy(in, out);

Exception in thread "main" java.lang.NullPointerException
at java.io.FilterInputStream.read(FilterInputStream.java:133)
at java.io.FilterInputStream.read(FilterInputStream.java:107)
at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:2146)
at org.apache.commons.io.IOUtils.copy(IOUtils.java:2102)
at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:2123)
at org.apache.commons.io.IOUtils.copy(IOUtils.java:2078)

However, if I change the input and output stream so they are files and not directories, then it copies fine.

Is there a way to recursively copy a directory that is part of the jar to the file system?

Putting all of your asset files into a zip file, as mentioned in the question to which Reddymails linked, is a pretty good way to do it.

If you want to keep the asset files as individual entries in your .jar, the problem is that you will not have a directory to list:

  • A directory entry in a .jar or zip file is just a name; there is no way to “list” it.
  • The .jar file is not always obtainable, because ProtectionDomain.getCodeSource() is allowed to return null .
  • There are complex ClassLoaders that read from sources other than directories or .jar files.

You can have your build process list the entries in a text file (since you know what they are, after all) before packaging them, and include that text file in your .jar. Then copying them at runtime is as easy as reading from that file:

Path assetDir = /* ... */;

try (BufferedReader listFile = new BufferedReader(
        new InputStreamReader(
            getClass().getResourceAsStream("assets-list.txt"),
            StandardCharsets.UTF_8))) {

    String assetResource;
    while ((assetResource = listFile.readLine()) != null) {
        Path assetFile = assetDir.resolve(assetResource);
        Files.createDirectories(assetFile.getParent());
        try (InputStream asset = getClass().getResourceAsStream(assetResource)) {
            Files.copy(asset, assetFile);
        }
    }
}

In case anyone is still looking for this, I just shared a class that does exactly this: copies a resource directory to a target location in the filesystem. It can be found here: https://stackoverflow.com/a/58318009/738968

If you are interested in copy a file from resources inside a JAR file
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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM