Comment obtenir un chemin vers une ressource dans un fichier JAR Java

166

J'essaie de trouver un chemin vers une ressource mais je n'ai pas eu de chance.

Cela fonctionne (à la fois dans l'IDE et avec le JAR) mais de cette façon, je ne peux pas obtenir un chemin vers un fichier, seulement le contenu du fichier:

ClassLoader classLoader = getClass().getClassLoader();
PrintInputStream(classLoader.getResourceAsStream("config/netclient.p"));

Si je fais ceci:

ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("config/netclient.p").getFile());

Le résultat est: java.io.FileNotFoundException: file:/path/to/jarfile/bot.jar!/config/netclient.p (No such file or directory)

Existe-t-il un moyen d'obtenir un chemin vers un fichier de ressources?

no_ripcord
la source
1
Oui. J'ai une classe avec laquelle je voudrais travailler à la fois, un dossier à l'extérieur (au cas où je voudrais changer un paramètre du fichier de configuration) et un JAR qui cache les fichiers de configuration de l'implémentation à l'utilisateur (comme un fichier distribuable JAR à tout le monde).
no_ripcord
1
Ainsi, la classe reçoit juste un PATH vers un fichier (le fichier de configuration).
no_ripcord
3
Ensuite, vous devriez probablement avoir cette classe avec un flux d'entrée, que vous pouvez obtenir de l'une ou l'autre source.
Carl Manaster
1
Oui je sais. Mais cela aurait été plus clair et plus propre dans l'autre sens. Mais merci quand même.
no_ripcord
1
Essayez-vous de faire quelque chose comme l'option n ° 4 dans cette question? stackoverflow.com/questions/775389/…
erickson

Réponses:

71

C'est délibéré. Le contenu du "fichier" peut ne pas être disponible sous forme de fichier. N'oubliez pas que vous avez affaire à des classes et des ressources qui peuvent faire partie d'un fichier JAR ou d'un autre type de ressource. Le chargeur de classe n'a pas à fournir de descripteur de fichier à la ressource, par exemple le fichier jar n'a peut-être pas été développé en fichiers individuels dans le système de fichiers.

Tout ce que vous pouvez faire en obtenant un fichier java.io.File peut être fait en copiant le flux dans un fichier temporaire et en faisant de même, si un fichier java.io.File est absolument nécessaire.

Neal Maloney
la source
6
vous pouvez ajouter 'rsrc:' lorsque vous appelez votre ressource afin de l'ouvrir. comme new File ("rsrc: filename.txt") cela chargera filename.txt qui est emballé à la racine de votre jar
gipsh
63

Lors du chargement d'une ressource, assurez-vous de noter la différence entre:

getClass().getClassLoader().getResource("com/myorg/foo.jpg") //relative path

et

getClass().getResource("/com/myorg/foo.jpg")); //note the slash at the beginning

Je suppose que cette confusion est à l'origine de la plupart des problèmes lors du chargement d'une ressource.


De plus, lorsque vous chargez une image, il est plus facile à utiliser getResourceAsStream():

BufferedImage image = ImageIO.read(getClass().getResourceAsStream("/com/myorg/foo.jpg"));

Lorsque vous devez vraiment charger un fichier (non-image) à partir d'une archive JAR, vous pouvez essayer ceci:

File file = null;
String resource = "/com/myorg/foo.xml";
URL res = getClass().getResource(resource);
if (res.getProtocol().equals("jar")) {
    try {
        InputStream input = getClass().getResourceAsStream(resource);
        file = File.createTempFile("tempfile", ".tmp");
        OutputStream out = new FileOutputStream(file);
        int read;
        byte[] bytes = new byte[1024];

        while ((read = input.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.close();
        file.deleteOnExit();
    } catch (IOException ex) {
        Exceptions.printStackTrace(ex);
    }
} else {
    //this will probably work in your IDE, but not from a JAR
    file = new File(res.getFile());
}

if (file != null && !file.exists()) {
    throw new RuntimeException("Error: File " + file + " not found!");
}
Tombart
la source
3
+1 Cela a fonctionné pour moi. Assurez-vous de placer les fichiers que vous souhaitez lire dans le bindossier et allez dans le répertoire de la classe se chargeant dans les ressources avant d'utiliser le chemin `/com/myorg/filename.ext '.
rayryeng
+1 Cela fonctionne aussi pour moi ... Je comprends qu'il peut y avoir des risques de sécurité liés à cette approche, donc les propriétaires d'applications doivent en être conscients.
LucasA
pourriez-vous clarifier cette déclaration "Il est toujours préférable de charger une ressource avec getResourceAsStream ()"? Comment cela peut-il apporter une solution au problème?
Luca S.
@LucaS. C'était juste pour le cas des images, désolé ce n'était pas très clair. L'approche devrait être indépendante de la plate-forme, bien que ce soit une manière un peu pirate.
Tombart
@LucasA Pourriez-vous clarifier les risques que vous avez mentionnés?
ZX9 du
25

La réponse en une ligne est -

String path = this.getClass().getClassLoader().getResource(<resourceFileName>).toExternalForm()

Fondamentalement, la getResourceméthode donne l'URL. Depuis cette URL, vous pouvez extraire le chemin en appelanttoExternalForm()

Références:

getResource () , toExternalForm ()

Manojkumar Khotele
la source
7
Lors de l'exécution à partir de mon environnement (IntelliJ), cela produit une URL de fichier simple qui fonctionne dans tous les cas. Cependant, lors de l'exécution à partir du fichier jar lui-même, j'obtiens un URI similaire à jar: file: /path/to/jar/jarname.jar! /File_in_jar.mp4. Tout ne peut pas utiliser un URI commençant par jar. Exemple concret JavaFX Media.
Noah Ternullo
1
J'aime mieux cette réponse. Certes, dans la plupart des cas, il peut être préférable de simplement récupérer l'InputStream sur la ressource lorsqu'elle se trouve dans un fichier jar, mais si pour une raison quelconque vous avez vraiment besoin du chemin, c'est ce qui fonctionne. J'avais besoin du chemin à donner à un objet tiers. Donc merci!
Mario
1
la solution ci-dessus ne fonctionne pas dans IDE, dans Intellijit ajoute file:/au chemin, qui fonctionne dans jar mais pas dans IDE
Tayab Hussain
Votre réponse a résolu un problème que j'essayais de résoudre depuis au moins 2 jours maintenant. TYVM !!
Jonathan
12

J'ai passé un moment à déconner avec ce problème, car aucune solution que j'ai trouvée ne fonctionnait, assez étrangement! Le répertoire de travail n'est souvent pas le répertoire du JAR, surtout si un JAR (ou tout autre programme, d'ailleurs) est exécuté à partir du menu Démarrer sous Windows. Voici donc ce que j'ai fait, et cela fonctionne aussi bien pour les fichiers .class exécutés depuis l'extérieur d'un JAR que pour un JAR. (Je l'ai testé uniquement sous Windows 7.)

try {
    //Attempt to get the path of the actual JAR file, because the working directory is frequently not where the file is.
    //Example: file:/D:/all/Java/TitanWaterworks/TitanWaterworks-en.jar!/TitanWaterworks.class
    //Another example: /D:/all/Java/TitanWaterworks/TitanWaterworks.class
    PROGRAM_DIRECTORY = getClass().getClassLoader().getResource("TitanWaterworks.class").getPath(); // Gets the path of the class or jar.

    //Find the last ! and cut it off at that location. If this isn't being run from a jar, there is no !, so it'll cause an exception, which is fine.
    try {
        PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(0, PROGRAM_DIRECTORY.lastIndexOf('!'));
    } catch (Exception e) { }

    //Find the last / and cut it off at that location.
    PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(0, PROGRAM_DIRECTORY.lastIndexOf('/') + 1);
    //If it starts with /, cut it off.
    if (PROGRAM_DIRECTORY.startsWith("/")) PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(1, PROGRAM_DIRECTORY.length());
    //If it starts with file:/, cut that off, too.
    if (PROGRAM_DIRECTORY.startsWith("file:/")) PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(6, PROGRAM_DIRECTORY.length());
} catch (Exception e) {
    PROGRAM_DIRECTORY = ""; //Current working directory instead.
}
DéProgrammeur
la source
8

s'il se netclient.ptrouve dans un fichier JAR, il n'aura pas de chemin car ce fichier se trouve dans un autre fichier. dans ce cas, le meilleur chemin que vous pouvez avoir est vraiment file:/path/to/jarfile/bot.jar!/config/netclient.p.

cd1
la source
Lorsque j'essaye de convertir une URL de ce format (... bot.jar! / Config / ...) en URI, cela indique que le chemin n'est pas hiérarchique.
gEdringer
7

Vous devez comprendre le chemin dans le fichier jar.
Faites simplement référence à lui relatif. Donc, si vous avez un fichier (myfile.txt), situé dans foo.jar sous le \src\main\resourcesrépertoire (style maven). Vous y feriez référence comme:

src/main/resources/myfile.txt

Si vous videz votre jar -tvf myjar.jar fichier jar en utilisant, vous verrez la sortie et le chemin relatif dans le fichier jar, et utilisez les barres obliques FORWARD.

Eric Manley
la source
Vous devez en effet utiliser des barres obliques, même sous Windows. Cela implique que vous ne pouvez pas utiliser File.separator.
str
3

Dans mon cas, j'ai utilisé un objet URL à la place Path.

Fichier

File file = new File("my_path");
URL url = file.toURI().toURL();

Ressource dans classpath à l'aide du classloader

URL url = MyClass.class.getClassLoader().getResource("resource_name")

Lorsque j'ai besoin de lire le contenu, je peux utiliser le code suivant:

InputStream stream = url.openStream();

Et vous pouvez accéder au contenu à l'aide d'un InputStream.

jmgoyesc
la source
3

Il s'agit du même code de l'utilisateur Tombart avec vidage et fermeture du flux pour éviter une copie incomplète du contenu du fichier temporaire à partir de la ressource jar et pour avoir des noms de fichier temporaires uniques.

File file = null;
String resource = "/view/Trial_main.html" ;
URL res = getClass().getResource(resource);
if (res.toString().startsWith("jar:")) {
    try {
        InputStream input = getClass().getResourceAsStream(resource);
        file = File.createTempFile(new Date().getTime()+"", ".html");
        OutputStream out = new FileOutputStream(file);
        int read;
        byte[] bytes = new byte[1024];

        while ((read = input.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.flush();
        out.close();
        input.close();
        file.deleteOnExit();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
} else {
    //this will probably work in your IDE, but not from a JAR
    file = new File(res.getFile());
}
         
Bruce
la source
2

Un fichier est une abstraction pour un fichier dans un système de fichiers, et les systèmes de fichiers ne savent rien du contenu d'un JAR.

Essayez avec un URI, je pense qu'il existe un protocole jar: // qui pourrait être utile pour vos objectifs.

fortran
la source
1

Le chemin suivant a fonctionné pour moi: classpath:/path/to/resource/in/jar

Anatoly
la source
1
@Anatoly Pourriez-vous s'il vous plaît partager plus de données ci
Kasun Siyambalapitiya
1
private static final String FILE_LOCATION = "com/input/file/somefile.txt";

//Method Body


InputStream invalidCharacterInputStream = URLClassLoader.getSystemResourceAsStream(FILE_LOCATION);

Obtenir ceci getSystemResourceAsStreamest la meilleure option. En obtenant le flux d'entrée plutôt que le fichier ou l'URL, fonctionne dans un fichier JAR et de manière autonome.

Shano
la source
1

Il est peut-être un peu tard mais vous pouvez utiliser ma bibliothèque KResourceLoader pour obtenir une ressource de votre jar:

File resource = getResource("file.txt")
Lamberto Basti
la source
0

Lorsqu'elle se trouve dans un fichier jar, la ressource se trouve absolument dans la hiérarchie du package (et non dans la hiérarchie du système de fichiers). Donc, si vous avez la classe com.example.Sweet chargeant une ressource nommée "./default.conf" alors le nom de la ressource est spécifié comme "/com/example/default.conf".

Mais si c'est dans un pot, ce n'est pas un fichier ...

Vilnis Krumins
la source
0

Dans votre dossier ressources (java / main / resources) de votre jar, ajoutez votre fichier (nous supposons que vous avez ajouté un fichier xml nommé imports.xml ), après cela vous injectez ResourceLoadersi vous utilisez spring comme ci-dessous

@Autowired
private ResourceLoader resourceLoader;

à l'intérieur de la fonction tour écrivez le code ci-dessous afin de charger le fichier:

    Resource resource = resourceLoader.getResource("classpath:imports.xml");
    try{
        File file;
        file = resource.getFile();//will load the file
...
    }catch(IOException e){e.printStackTrace();}
BERGUIGA Mohamed Amine
la source
0

Peut-être que cette méthode peut être utilisée pour une solution rapide.

public class TestUtility
{ 
    public static File getInternalResource(String relativePath)
    {
        File resourceFile = null;
        URL location = TestUtility.class.getProtectionDomain().getCodeSource().getLocation();
        String codeLocation = location.toString();
        try{
            if (codeLocation.endsWith(".jar"){
                //Call from jar
                Path path = Paths.get(location.toURI()).resolve("../classes/" + relativePath).normalize();
                resourceFile = path.toFile();
            }else{
                //Call from IDE
                resourceFile = new File(TestUtility.class.getClassLoader().getResource(relativePath).getPath());
            }
        }catch(URISyntaxException ex){
            ex.printStackTrace();
        }
        return resourceFile;
    }
}
gbii
la source
Oubliez-vous un certain contexte? Cela me donne:java.lang.NullPointerException: Attempt to invoke virtual method 'java.security.CodeSource java.security.ProtectionDomain.getCodeSource()' on a null object reference
Allen Luce
J'ai édité la réponse et écrit toute la méthode que j'ai utilisée dans un logiciel
gbii
0

suivez le code!

/ src / main / resources / fichier

streamToFile(getClass().getClassLoader().getResourceAsStream("file"))

public static File streamToFile(InputStream in) {
    if (in == null) {
        return null;
    }

    try {
        File f = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
        f.deleteOnExit();

        FileOutputStream out = new FileOutputStream(f);
        byte[] buffer = new byte[1024];

        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }

        return f;
    } catch (IOException e) {
        LOGGER.error(e.getMessage(), e);
        return null;
    }
}
seunggabi
la source