Come ottenere un percorso per una risorsa in un file JAR Java


166

Sto cercando di ottenere un percorso per una risorsa ma non ho avuto fortuna.

Funziona (sia in IDE che con JAR) ma in questo modo non riesco a ottenere un percorso per un file, solo il contenuto del file:

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

Se lo faccio:

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

Il risultato è: java.io.FileNotFoundException: file:/path/to/jarfile/bot.jar!/config/netclient.p (No such file or directory)

C'è un modo per ottenere un percorso a un file di risorse?


1
Sì. Ho una classe con cui mi piacerebbe lavorare con entrambi, una cartella all'esterno (nel caso in cui volessi cambiare alcuni parametri del file di configurazione) e un JAR che nasconde all'utente i file di configurazione dell'implementazione (come un distribuibile JAR a tutte le persone).
no_ripcord,

1
Quindi la classe riceve solo un PERCORSO su un file (il file di configurazione).
no_ripcord,

3
Quindi probabilmente dovresti avere quel patto di classe con un flusso di input, che puoi ottenere da entrambe le fonti.
Carl Manaster,

1
Si, lo so. Ma sarebbe stato più chiaro e pulito dall'altra parte. Ma grazie comunque.
no_ripcord,

1
Stai provando a fare qualcosa come l'opzione n. 4 in questa domanda? stackoverflow.com/questions/775389/...
erickson

Risposte:


71

Questo è deliberato. Il contenuto del "file" potrebbe non essere disponibile come file. Ricorda che hai a che fare con classi e risorse che potrebbero far parte di un file JAR o di un altro tipo di risorsa. Il classloader non deve fornire un handle di file alla risorsa, ad esempio il file jar potrebbe non essere stato espanso in singoli file nel file system.

Tutto ciò che puoi fare ottenendo un file java.io.File potrebbe essere fatto copiando il flusso in un file temporaneo e facendo lo stesso, se un file java.io.File è assolutamente necessario.


6
puoi aggiungere 'rsrc:' quando chiami la tua risorsa per aprirla. come il nuovo File ("rsrc: filename.txt") questo caricherà il nome file.txt che è impacchettato nella radice del tuo vaso
gipsh

63

Quando si carica una risorsa, assicurarsi di notare la differenza tra:

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

e

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

Immagino che questa confusione stia causando la maggior parte dei problemi durante il caricamento di una risorsa.


Inoltre, quando carichi un'immagine è più facile da usare getResourceAsStream():

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

Quando devi davvero caricare un file (non immagine) da un archivio JAR, puoi provare questo:

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

3
+1 Questo ha funzionato per me. Assicurati di inserire i file che vuoi leggere nella bincartella e vai nella directory della classe che carica le risorse prima di usare il percorso `/com/myorg/filename.ext '.
Rayryeng,

+1 Questo funziona anche per me ... Capisco che potrebbero esserci alcuni rischi per la sicurezza legati a questo approccio, quindi i proprietari delle applicazioni devono esserne consapevoli.
Lucas,

potresti chiarire questa affermazione "È sempre meglio caricare una risorsa con getResourceAsStream ()"? Come può fornire una soluzione al problema?
Luca S.

@LucaS. Questo era pensato solo per il caso delle immagini, mi spiace non fosse molto chiaro. L'approccio dovrebbe essere indipendente dalla piattaforma, sebbene sia un po 'confuso.
Tombart,

@LucasA Potresti chiarire i rischi che hai citato?
ZX9,

25

La risposta di una riga è:

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

Fondamentalmente il getResourcemetodo fornisce l'URL. Da questo URL è possibile estrarre il percorso chiamandotoExternalForm()

Riferimenti:

getResource () , toExternalForm ()


7
Durante l'esecuzione dal mio ambiente (IntelliJ) questo produce un semplice URL di file che funziona in tutti i casi. Tuttavia, quando eseguo dal jar stesso, ottengo un URI simile a jar: file: /path/to/jar/jarname.jar! /File_in_jar.mp4. Non tutto può utilizzare un URI che inizia con jar. Caso in questione JavaFX Media.
Noah Ternullo,

1
Questa risposta mi piace di più. Certo, nella maggior parte dei casi potrebbe essere preferibile prendere semplicemente InputStream nella risorsa quando si trova in un file jar, ma se per qualche ragione hai davvero bisogno del percorso, questo è ciò che funziona. Avevo bisogno del percorso da dare a un oggetto di terze parti. Quindi grazie!
Mario,

1
la soluzione sopra non funziona mentre in IDE, in Intellijit aggiunge file:/al percorso, che funziona in jar ma non in IDE
Tayab Hussain

La tua risposta ha risolto un problema che avevo cercato di risolvere da almeno 2 giorni. TYVM !!
Jonathan

12

Ho passato un po 'a scherzare con questo problema, perché nessuna soluzione che ho trovato effettivamente funzionava, stranamente! La directory di lavoro spesso non è la directory del JAR, specialmente se un JAR (o qualsiasi programma, per quella materia) viene eseguito dal menu Start in Windows. Quindi, ecco cosa ho fatto e funziona per i file .class eseguiti dall'esterno di un JAR, così come funziona per un JAR. (L'ho provato solo su 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.
}

8

se si netclient.ptrova all'interno di un file JAR, non avrà un percorso perché quel file si trova all'interno di un altro file. in tal caso, il percorso migliore che puoi avere è davvero file:/path/to/jarfile/bot.jar!/config/netclient.p.


Quando provo a convertire un URL di questo formato (... bot.jar! / Config / ...) in URI, dice che il percorso non è gerarchico.
gEdringer,

7

È necessario comprendere il percorso all'interno del file jar.
Basta fare riferimento ad esso relativo. Quindi se hai un file (myfile.txt), situato in foo.jar nella \src\main\resourcesdirectory (stile maven). Ti riferiresti ad esso come:

src/main/resources/myfile.txt

Se scarichi il tuo jar usando jar -tvf myjar.jar vedrai l'output e il relativo percorso all'interno del file jar e userai FORWARD SLASHES.


Devi davvero usare le barre, anche su Windows. Ciò implica che non è possibile utilizzare File.separator.
str

3

Nel mio caso, ho usato un oggetto URL anziché Path.

File

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

Risorsa in classpath utilizzando classloader

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

Quando ho bisogno di leggere il contenuto, posso usare il seguente codice:

InputStream stream = url.openStream();

E puoi accedere al contenuto usando un InputStream.


3

Questo è lo stesso codice dell'utente Tombart con flusso flush e close per evitare la copia incompleta del contenuto del file temporaneo dalla risorsa jar e avere nomi di file temporanei univoci.

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

2

Un file è un'astrazione per un file in un filesystem e i filesystem non sanno nulla di quali siano i contenuti di un JAR.

Prova con un URI, penso che ci sia un protocollo jar: // che potrebbe essere utile per i tuoi scopi.



1
private static final String FILE_LOCATION = "com/input/file/somefile.txt";

//Method Body


InputStream invalidCharacterInputStream = URLClassLoader.getSystemResourceAsStream(FILE_LOCATION);

Ottenere questo da getSystemResourceAsStreamè l'opzione migliore. Ottenendo l'inputstream anziché il file o l'URL, funziona in un file JAR e come indipendente.


1

Potrebbe essere un po 'tardi, ma puoi usare la mia libreria KResourceLoader per ottenere una risorsa dal tuo vaso:

File resource = getResource("file.txt")

0

Quando si trova in un file jar, la risorsa si trova assolutamente nella gerarchia del pacchetto (non nella gerarchia del file system). Pertanto, se si dispone della classe com.example.Sweet che carica una risorsa denominata "./default.conf", il nome della risorsa viene specificato come "/com/example/default.conf".

Ma se è in un barattolo, allora non è un file ...


0

All'interno della tua cartella risorse (java / main / resources) del tuo vaso aggiungi il tuo file (assumiamo che tu abbia aggiunto un file xml chiamato imports.xml ), dopo di che ti inietti ResourceLoaderse usi spring like bellow

@Autowired
private ResourceLoader resourceLoader;

all'interno della funzione tour scrivi il codice seguente per caricare il file:

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

0

Forse questo metodo può essere utilizzato per una soluzione rapida.

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

Stai omettendo qualche contesto? Questo mi dà:java.lang.NullPointerException: Attempt to invoke virtual method 'java.security.CodeSource java.security.ProtectionDomain.getCodeSource()' on a null object reference
Allen Luce,

Ho modificato la risposta e scritto l'intero metodo che ho usato in un software
gbii

0

segui il codice!

/ / / Risorse / file principale src

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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.