Jak uzyskać ścieżkę do zasobu w pliku Java JAR

166

Próbuję znaleźć ścieżkę do zasobu, ale nie miałem szczęścia.

To działa (zarówno w IDE, jak i z JAR), ale w ten sposób nie mogę uzyskać ścieżki do pliku, tylko zawartość pliku:

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

Jeśli to zrobię:

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

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

Czy istnieje sposób na uzyskanie ścieżki do pliku zasobów?

no_ripcord
źródło
1
Tak. Mam klasę, z którą chciałbym pracować zarówno z folderem zewnętrznym (na wypadek, gdy chcę zmienić jakiś parametr pliku konfiguracyjnego), jak i JAR, który ukrywa pliki konfiguracyjne implementacji dla użytkownika (jak dystrybuowalny JAR dla wszystkich ludzi).
no_ripcord
1
Więc klasa po prostu otrzymuje PATH do pliku (pliku konfiguracyjnego).
no_ripcord
3
Wtedy prawdopodobnie powinieneś mieć tę klasę do czynienia ze strumieniem wejściowym, który możesz uzyskać z dowolnego źródła.
Carl Manaster
1
Tak, wiem. Ale z drugiej strony byłoby to bardziej przejrzyste i czyste. Ale dzięki tak.
no_ripcord
1
Czy próbujesz zrobić coś podobnego do opcji nr 4 w tym pytaniu? stackoverflow.com/questions/775389/…
erickson

Odpowiedzi:

71

To jest celowe. Zawartość „pliku” może nie być dostępna jako plik. Pamiętaj, że masz do czynienia z klasami i zasobami, które mogą być częścią pliku JAR lub innego rodzaju zasobu. Program ładujący klasy nie musi udostępniać uchwytu pliku do zasobu, na przykład plik jar mógł nie zostać rozszerzony na pojedyncze pliki w systemie plików.

Wszystko, co możesz zrobić, pobierając plik java.io.File, można zrobić, kopiując strumień do pliku tymczasowego i robiąc to samo, jeśli plik java.io.File jest absolutnie konieczny.

Neal Maloney
źródło
6
możesz dodać „rsrc:”, gdy dzwonisz do swojego zasobu, aby go otworzyć. jak nowy plik („rsrc: filename.txt”) to załaduje nazwę pliku.txt, który jest spakowany w katalogu głównym twojego jar
gipsh
63

Podczas ładowania zasobu zwróć uwagę na różnicę między:

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

i

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

Myślę, że to zamieszanie powoduje większość problemów podczas ładowania zasobu.


Ponadto podczas ładowania obrazu jest łatwiejszy w użyciu getResourceAsStream():

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

Jeśli naprawdę musisz załadować plik (inny niż obraz) z archiwum JAR, możesz spróbować tego:

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
źródło
3
+1 To zadziałało dla mnie. binPrzed użyciem ścieżki `/com/myorg/filename.ext ' umieść pliki, które chcesz odczytać w folderze i przejdź do katalogu klasy ładującej się do zasobów.
rayryeng
+1 To również działa w moim przypadku ... Rozumiem, że z tym podejściem mogą wiązać się pewne zagrożenia bezpieczeństwa, więc właściciele aplikacji muszą być tego świadomi.
LucasA
czy mógłbyś wyjaśnić tę instrukcję „Zawsze lepiej jest ładować zasób za pomocą metody getResourceAsStream ()”? W jaki sposób może to zapewnić rozwiązanie problemu?
Luca S.
@LucaS. To było przeznaczone tylko do zdjęć, przepraszam, że nie było to zbyt jasne. Podejście powinno być niezależne od platformy, choć jest to nieco hakerskie podejście.
Tombart
@LucasA Czy mógłbyś wyjaśnić wspomniane ryzyko?
ZX9
25

Jedna linia odpowiedzi brzmi:

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

Zasadniczo getResourcemetoda podaje adres URL. Z tego adresu URL możesz wyodrębnić ścieżkę, dzwoniąctoExternalForm()

Bibliografia:

getResource () , toExternalForm ()

Manojkumar Khotele
źródło
7
Podczas uruchamiania z mojego środowiska (IntelliJ) tworzy to prosty adres URL pliku, który działa we wszystkich przypadkach. Jednak podczas uruchamiania z samego jar, otrzymuję identyfikator URI podobny do jar: file: /path/to/jar/jarname.jar! /File_in_jar.mp4. Nie wszystko może wykorzystywać identyfikator URI zaczynający się od jar. Przykładem jest JavaFX Media.
Noah Ternullo
1
Najbardziej podoba mi się ta odpowiedź. To prawda, w większości przypadków może być lepiej po prostu pobrać InputStream do zasobu, gdy znajduje się w pliku jar, ale jeśli z jakiegoś powodu naprawdę potrzebujesz ścieżki, to działa. Potrzebowałem ścieżki do udostępnienia obiektowi innej firmy. Więc dziękuję!
Mario
1
powyższe rozwiązanie nie działa podczas gdy w IDE, w Intellijit dodaje file:/do ścieżki, która działa w jar, ale nie w IDE
Tayab Hussain
Twoja odpowiedź rozwiązała problem, nad którym pracowałem od co najmniej 2 dni. TYVM !!
Jonathan
12

Spędziłem trochę czasu, bawiąc się tym problemem, ponieważ żadne rozwiązanie, które znalazłem, w rzeczywistości nie działało, o dziwo! Katalog roboczy często nie jest katalogiem JAR, zwłaszcza jeśli plik JAR (lub jakikolwiek inny program) jest uruchamiany z menu Start w systemie Windows. Oto, co zrobiłem, i działa to w przypadku plików .class uruchamianych spoza JAR, tak samo jak działa w przypadku JAR. (Testowałem to tylko pod 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.
}
DeProgrammer
źródło
8

jeśli netclient.pznajduje się w pliku JAR, nie będzie miał ścieżki, ponieważ ten plik znajduje się w innym pliku. w takim przypadku najlepsza ścieżka, jaką możesz mieć, to naprawdę file:/path/to/jarfile/bot.jar!/config/netclient.p.

cd1
źródło
Kiedy próbuję przekonwertować adres URL tego formatu (... bot.jar! / Config / ...) na URI, mówi, że ścieżka nie jest hierarchiczna.
gEdringer
7

Musisz zrozumieć ścieżkę w pliku jar.
Po prostu określ go względnym. Więc jeśli masz plik (myfile.txt), znajdujący się w foo.jar w \src\main\resourceskatalogu (styl maven). Można to odnieść tak:

src/main/resources/myfile.txt

Jeśli zrzucisz swój jar za pomocą jar -tvf myjar.jar , zobaczysz wyjście i względną ścieżkę w pliku jar i użyjesz FORWARD SLASHES.

eric manley
źródło
Rzeczywiście, musisz używać ukośników, nawet w systemie Windows. Oznacza to, że nie możesz używać File.separator.
str
3

W moim przypadku zamiast Path użyłem obiektu URL.

Plik

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

Zasób w classpath przy użyciu classloader

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

Gdy potrzebuję przeczytać treść, mogę skorzystać z następującego kodu:

InputStream stream = url.openStream();

Możesz uzyskać dostęp do treści za pomocą InputStream.

jmgoyesc
źródło
3

To ten sam kod od użytkownika Tombart z opróżnieniem strumienia i zamknięciem, aby uniknąć niekompletnego kopiowania zawartości pliku tymczasowego z zasobu jar i mieć unikalne nazwy plików tymczasowych.

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
źródło
2

Plik jest abstrakcją dla pliku w systemie plików, a systemy plików nie wiedzą nic o zawartości pliku JAR.

Spróbuj z URI, myślę, że istnieje protokół jar: //, który może być przydatny do twoich celów.

fortran
źródło
1

Następująca ścieżka zadziałała dla mnie: classpath:/path/to/resource/in/jar

Anatolij
źródło
1
@Anatoly Czy mógłbyś podać więcej danych powyżej
Kasun Siyambalapitiya
1
private static final String FILE_LOCATION = "com/input/file/somefile.txt";

//Method Body


InputStream invalidCharacterInputStream = URLClassLoader.getSystemResourceAsStream(FILE_LOCATION);

Zdobycie tego getSystemResourceAsStreamjest najlepszą opcją. Pobierając strumień wejściowy zamiast pliku lub adresu URL, działa w pliku JAR i jako samodzielny.

Shano
źródło
1

Może być trochę późno, ale możesz użyć mojej biblioteki KResourceLoader, aby pobrać zasób ze swojego jar:

File resource = getResource("file.txt")
Lamberto Basti
źródło
0

W pliku jar zasób znajduje się absolutnie w hierarchii pakietów (nie w hierarchii systemu plików). Jeśli więc masz klasę com.example.Sweet ładującą zasób o nazwie „./default.conf”, wówczas nazwa zasobu jest określana jako „/com/example/default.conf”.

Ale jeśli jest w słoiku, to nie jest to plik ...

Vilnis Krumins
źródło
0

Wewnątrz folderu zasobów (java / main / resources) twojego jar dodaj swój plik (zakładamy, że dodałeś plik xml o nazwie imports.xml ), a następnie wstrzyknij, ResourceLoaderjeśli używasz sprężyny jak poniżej

@Autowired
private ResourceLoader resourceLoader;

Funkcja inside tour napisz poniższy kod, aby załadować plik:

    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
źródło
0

Może tę metodę można zastosować do szybkiego rozwiązania.

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
źródło
Czy pomijasz jakiś kontekst? To daje mi:java.lang.NullPointerException: Attempt to invoke virtual method 'java.security.CodeSource java.security.ProtectionDomain.getCodeSource()' on a null object reference
Allen Luce
Zredagowałem odpowiedź i napisałem całą metodę, której użyłem w oprogramowaniu
gbii
0

podążaj za kodem!

/ src / main / resources / file

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
źródło