web-dev-qa-db-fra.com

Lecture d'un fichier de ressources à partir du fichier jar

Je voudrais lire une ressource de mon pot comme ceci:

File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferredReader reader = new BufferedReader(new FileReader(file));

//Read the file

et cela fonctionne bien quand on l'exécute dans Eclipse, mais si je l'exporte dans un fichier jar, il y a une exception IllegalArgumentException:

Exception in thread "Thread-2"
Java.lang.IllegalArgumentException: URI is not hierarchical

et je ne sais vraiment pas pourquoi mais avec quelques tests j'ai trouvé si je change

file = new File(getClass().getResource("/file.txt").toURI());

à

file = new File(getClass().getResource("/folder/file.txt").toURI());

alors ça marche le contraire (ça marche en pot mais pas en Eclipse).

J'utilise Eclipse et le dossier contenant mon fichier se trouve dans un dossier de classe.

197
PrinceCJC

Plutôt que d'essayer de traiter la ressource en tant que Fichier , demandez simplement à ClassLoader de renvoyer un InputStream pour la ressource via getResourceAsStream :

InputStream in = getClass().getResourceAsStream("/file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

Tant que la ressource file.txt est disponible sur le chemin de classe, cette approche fonctionnera de la même manière, que la ressource file.txt se trouve dans un répertoire classes/ ou à l'intérieur d'un jar .

Le URI is not hierarchical se produit car l'URI d'une ressource dans un fichier JAR va ressembler à ceci: file:/example.jar!/file.txt. Vous ne pouvez pas lire les entrées d'un fichier jar (un fichier Zip) comme s'il s'agissait d'un ancien vieux Fichier .

Ceci s’explique bien par les réponses à:

332
Drew MacInnis

Pour accéder à un fichier dans un fichier jar, vous avez deux options:

  • Placez le fichier dans la structure de répertoires correspondant au nom de votre package (après avoir extrait le fichier .jar, il doit se trouver dans le même répertoire que le fichier .class), puis accédez-y à l'aide de getClass().getResourceAsStream("file.txt").

  • Placez le fichier à la racine (après avoir extrait le fichier .jar, il devrait être à la racine), puis accédez-y à l'aide de Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")

La première option peut ne pas fonctionner lorsque jar est utilisé comme plugin.

17
Juozas Kontvainis

Si vous voulez lire comme un fichier, je crois qu'il existe toujours une solution similaire:

    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("file/test.xml").getFile());
5
pablo.vix

J'ai eu ce problème avant et j'ai fait de manière de secours pour le chargement. Fondamentalement, la première méthode fonctionne dans un fichier .jar et la deuxième méthode dans Eclipse ou un autre IDE.

public class MyClass {

    public static InputStream accessFile() {
        String resource = "my-file-located-in-resources.txt";

        // this is the path within the jar file
        InputStream input = MyClass.class.getResourceAsStream("/resources/" + resource);
        if (input == null) {
            // this is how we load file within editor (eg Eclipse)
            input = MyClass.class.getClassLoader().getResourceAsStream(resource);
        }

        return input;
    }
}
3
MForm

Assurez-vous de travailler avec le bon séparateur. J'ai remplacé tous les / d'un chemin relatif par un File.separator. Cela a bien fonctionné dans l'IDE, mais n'a pas fonctionné dans le fichier JAR de génération.

1
Petterson

Jusqu'à présent (décembre 2017), c'est la seule solution que j'ai trouvée qui fonctionne les deux à l'intérieur et à l'extérieur de l'IDE.

Utilisez PathMatchingResourcePatternResolver

Note: cela fonctionne aussi dans spring-boot

Dans cet exemple, je lis des fichiers situés dans src/main/resources/my_folder:

try {
    // Get all the files under this inner resource folder: my_folder
    String scannedPackage = "my_folder/*";
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    Resource[] resources = scanner.getResources(scannedPackage);

    if (resources == null || resources.length == 0)
        log.warn("Warning: could not find any resources in this scanned package: " + scannedPackage);
    else {
        for (Resource resource : resources) {
            log.info(resource.getFilename());
            // Read the file content (I used BufferedReader, but there are other solutions for that):
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                // ...
                // ...                      
            }
            bufferedReader.close();
        }
    }
} catch (Exception e) {
    throw new Exception("Failed to read the resources folder: " + e.getMessage(), e);
}
1
Naor Bar

Après de nombreuses recherches en Java, la seule solution qui semble fonctionner pour moi est de lire manuellement le fichier jar lui-même, sauf si vous vous trouvez dans un environnement de développement (IDE):

/** @return The root folder or jar file that the class loader loaded from */
public static final File getClasspathFile() {
    return new File(YourMainClass.class.getProtectionDomain().getCodeSource().getLocation().getFile());
}

/** @param resource The path to the resource
 * @return An InputStream containing the resource's contents, or
 *         <b><code>null</code></b> if the resource does not exist */
public static final InputStream getResourceAsStream(String resource) {
    resource = resource.startsWith("/") ? resource : "/" + resource;
    if(getClasspathFile().isDirectory()) {//Development environment:
        return YourMainClass.class.getResourceAsStream(resource);
    }
    final String res = resource;//Jar or exe:
    return AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
        @SuppressWarnings("resource")
        @Override
        public InputStream run() {
            try {
                final JarFile jar = new JarFile(getClasspathFile());
                String resource = res.startsWith("/") ? res.substring(1) : res;
                if(resource.endsWith("/")) {//Directory; list direct contents:(Mimics normal getResourceAsStream("someFolder/") behaviour)
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    Enumeration<JarEntry> entries = jar.entries();
                    while(entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        if(entry.getName().startsWith(resource) && entry.getName().length() > resource.length()) {
                            String name = entry.getName().substring(resource.length());
                            if(name.contains("/") ? (name.endsWith("/") && (name.indexOf("/") == name.lastIndexOf("/"))) : true) {//If it's a folder, we don't want the children's folders, only the parent folder's children!
                                name = name.endsWith("/") ? name.substring(0, name.length() - 1) : name;
                                baos.write(name.getBytes(StandardCharsets.UTF_8));
                                baos.write('\r');
                                baos.write('\n');
                            }
                        }
                    }
                    jar.close();
                    return new ByteArrayInputStream(baos.toByteArray());
                }
                JarEntry entry = jar.getJarEntry(resource);
                InputStream in = entry != null ? jar.getInputStream(entry) : null;
                if(in == null) {
                    jar.close();
                    return in;
                }
                final InputStream stream = in;//Don't manage 'jar' with try-with-resources or close jar until the
                return new InputStream() {//returned stream is closed(closing the jar closes all associated InputStreams):
                    @Override
                    public int read() throws IOException {
                        return stream.read();
                    }

                    @Override
                    public int read(byte b[]) throws IOException {
                        return stream.read(b);
                    }

                    @Override
                    public int read(byte b[], int off, int len) throws IOException {
                        return stream.read(b, off, len);
                    }

                    @Override
                    public long skip(long n) throws IOException {
                        return stream.skip(n);
                    }

                    @Override
                    public int available() throws IOException {
                        return stream.available();
                    }

                    @Override
                    public void close() throws IOException {
                        try {
                            jar.close();
                        } catch(IOException ignored) {
                        }
                        stream.close();
                    }

                    @Override
                    public synchronized void mark(int readlimit) {
                        stream.mark(readlimit);
                    }

                    @Override
                    public synchronized void reset() throws IOException {
                        stream.reset();
                    }

                    @Override
                    public boolean markSupported() {
                        return stream.markSupported();
                    }
                };
            } catch(Throwable e) {
                e.printStackTrace();
                return null;
            }
        }
    });
}

Note: Le code ci-dessus ne semble fonctionner correctement pour les fichiers jar que s'il est dans la classe principale. Je ne sais pas pourquoi.

0
Brian_Entei

Le code ci-dessous fonctionne avec Spring Boot (kotlin):

val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))
0
Bikash Das

Si vous utilisez spring, vous pouvez utiliser la méthode suivante pour lire le fichier depuis src/main/resources:

import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import org.springframework.core.io.ClassPathResource;

  public String readFileToString(String path) throws IOException {

    StringBuilder resultBuilder = new StringBuilder("");
    ClassPathResource resource = new ClassPathResource(path);

    try (
        InputStream inputStream = resource.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {

      String line;

      while ((line = bufferedReader.readLine()) != null) {
        resultBuilder.append(line);
      }

    }

    return resultBuilder.toString();
  }
0
Sujan M.

Je pense que cela devrait également fonctionner dans Java. Le code suivant que j'utilise utilise kotlin.

val resource = Thread.currentThread().contextClassLoader.getResource('resources.txt')
0
irvifa

Vous pouvez utiliser le chargeur de classe qui lit le classpath en tant que chemin ROOT (sans "/" au début)

InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
0
sendon1982

Le problème est que certaines bibliothèques tierces nécessitent des noms de chemin de fichiers plutôt que des flux d'entrée. La plupart des réponses n'abordent pas ce problème.

Dans ce cas, une solution de contournement consiste à copier le contenu de la ressource dans un fichier temporaire. L'exemple suivant utilise TemporaryFolder de jUnit.

    private List<String> decomposePath(String path){
        List<String> reversed = Lists.newArrayList();
        File currFile = new File(path);
        while(currFile != null){
            reversed.add(currFile.getName());
            currFile = currFile.getParentFile();
        }
        return Lists.reverse(reversed);
    }

    private String writeResourceToFile(String resourceName) throws IOException {
        ClassLoader loader = getClass().getClassLoader();
        InputStream configStream = loader.getResourceAsStream(resourceName);
        List<String> pathComponents = decomposePath(resourceName);
        folder.newFolder(pathComponents.subList(0, pathComponents.size() - 1).toArray(new String[0]));
        File tmpFile = folder.newFile(resourceName);
        Files.copy(configStream, tmpFile.toPath(), REPLACE_EXISTING);
        return tmpFile.getAbsolutePath();
    }
0
Navneeth

Pour une raison quelconque, classLoader.getResource() renvoyait toujours la valeur null lorsque j'ai déployé l'application Web dans WildFly 14. L'obtention de classLoader à partir de getClass().getClassLoader() ou Thread.currentThread().getContextClassLoader() renvoie null.

getClass().getClassLoader() doc API dit,

"Retourne le chargeur de classe pour la classe. Certaines implémentations peuvent utiliser null pour représenter le chargeur de classe bootstrap. Cette méthode renvoie null dans ces implémentations si cette classe a été chargée par la classe bootstrap chargeur."

peut-être si vous utilisez WildFly et votre application Web, essayez ceci

request.getServletContext().getResource() a renvoyé l'URL de la ressource. Ici, la demande est un objet de ServletRequest.

0
Ram C