Cara mendapatkan jalur ke sumber daya dalam file Java JAR

166

Saya mencoba untuk mendapatkan jalan ke Sumber Daya tetapi saya tidak beruntung.

Ini berfungsi (baik dalam IDE dan dengan JAR) tetapi dengan cara ini saya tidak bisa mendapatkan path ke file, hanya isi file:

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

Jika saya melakukan ini:

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

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

Apakah ada cara untuk mendapatkan path ke file sumber daya?

no_ripcord
sumber
1
Iya. Saya punya kelas yang ingin saya gunakan, folder di luar (kalau-kalau saya ingin mengubah beberapa parameter dari file konfigurasi) dan JAR yang menyembunyikan file konfigurasi implementasi ke pengguna (seperti yang dapat didistribusikan JAR untuk semua orang).
no_ripcord
1
Jadi kelas hanya menerima PATH ke file (file konfigurasi).
no_ripcord
3
Maka Anda mungkin harus memiliki kesepakatan kelas dengan aliran input, yang bisa Anda dapatkan dari sumber mana pun.
Carl Manaster
1
Ya saya tahu. Tapi itu akan lebih jelas dan bersih dengan cara lain. Tapi bagaimanapun juga.
no_ripcord
1
Apakah Anda mencoba melakukan sesuatu seperti opsi # 4 dalam pertanyaan ini? stackoverflow.com/questions/775389/…
erickson

Jawaban:

71

Ini disengaja. Konten "file" mungkin tidak tersedia sebagai file. Ingat Anda berurusan dengan kelas dan sumber daya yang mungkin merupakan bagian dari file JAR atau jenis sumber daya lainnya. Classloader tidak harus menyediakan pegangan file ke sumber daya, misalnya file jar mungkin tidak diperluas ke file individual di sistem file.

Apa pun yang dapat Anda lakukan dengan mendapatkan java.io.File dapat dilakukan dengan menyalin aliran ke file sementara dan melakukan hal yang sama, jika java.io.File benar-benar diperlukan.

Neal Maloney
sumber
6
Anda dapat menambahkan 'rsrc:' saat Anda memanggil sumber daya Anda untuk membukanya. seperti File baru ("rsrc: filename.txt") ini akan memuat filename.txt yang dikemas di dalam root toples Anda
gipsh
63

Saat memuat sumber daya, pastikan Anda memperhatikan perbedaan antara:

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

dan

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

Saya kira, kebingungan ini menyebabkan sebagian besar masalah saat memuat sumber daya.


Selain itu, saat Anda memuat gambar, lebih mudah digunakan getResourceAsStream():

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

Ketika Anda benar-benar harus memuat file (non-gambar) dari arsip JAR, Anda dapat mencoba ini:

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
sumber
3
+1 Ini berfungsi untuk saya. Pastikan Anda menempatkan file yang ingin Anda baca di binfolder dan pergi ke direktori pemuatan kelas di sumber daya sebelum menggunakan jalur `/com/myorg/filename.ext '.
rayryeng
+1 Ini juga berfungsi untuk saya ... Saya mengerti bahwa mungkin ada beberapa risiko keamanan yang terkait dengan pendekatan ini, jadi pemilik aplikasi perlu mengetahui hal itu.
LucasA
dapatkah Anda memperjelas pernyataan ini "Selalu lebih baik memuat sumber daya dengan getResourceAsStream ()"? Bagaimana ini bisa memberikan solusi untuk masalah tersebut?
Luca S.
@LucaS Itu dimaksudkan hanya untuk kasus gambar, maaf itu tidak terlalu jelas. Pendekatannya harus independen terhadap platform, meskipun cara ini agak rumit.
Tombart
@LucasA Bisakah Anda mengklarifikasi risiko yang Anda sebutkan?
ZX9
25

Jawaban satu baris adalah -

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

Pada dasarnya getResourcemetode memberikan URL. Dari URL ini Anda dapat mengekstrak jalur dengan menelepontoExternalForm()

Referensi:

getResource () , toExternalForm ()

Manojkumar Khotele
sumber
7
Saat berjalan dari lingkungan saya (IntelliJ) ini menghasilkan URL file sederhana yang berfungsi dalam semua kasus. Namun, ketika menjalankan dari toples itu sendiri, saya mendapatkan URI yang mirip dengan toples: file: /path/to/jar/jarname.jar! /File_in_jar.mp4. Tidak semua bisa memanfaatkan URI yang dimulai dengan toples. Contoh kasus JavaFX Media.
Noah Ternullo
1
Saya suka jawaban ini. Memang, dalam banyak kasus mungkin lebih baik untuk hanya mengambil InputStream ke sumber daya ketika itu dalam file jar, tetapi jika karena alasan tertentu Anda benar-benar membutuhkan jalan, ini yang berhasil. Saya membutuhkan jalan untuk diberikan kepada objek pihak ketiga. Jadi terima kasih!
Mario
1
solusi di atas tidak berfungsi saat di IDE, di Intellijit menambah file:/path, yang bekerja di jar tetapi tidak di IDE
Tayab Hussain
Respons Anda memecahkan masalah yang saya coba selesaikan selama setidaknya 2 hari sekarang. TYVM !!
Jonathan
12

Saya menghabiskan waktu dengan bermain-main dengan masalah ini, karena tidak ada solusi yang saya temukan benar-benar berhasil, anehnya! Direktori kerja sering kali bukan direktori JAR, terutama jika JAR (atau program apa pun) dijalankan dari Start Menu di bawah Windows. Jadi di sini adalah apa yang saya lakukan, dan itu berfungsi untuk file .class dijalankan dari luar JAR sama seperti itu bekerja untuk JAR. (Saya hanya mengujinya di 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
sumber
8

jika netclient.pada di dalam file JAR, itu tidak akan memiliki jalur karena file itu terletak di dalam file lain. dalam hal itu, jalan terbaik yang dapat Anda miliki adalah benar-benar file:/path/to/jarfile/bot.jar!/config/netclient.p.

cd1
sumber
Ketika saya mencoba dan mengonversi URL dari format ini (... bot.jar! / Config / ...) ke URI ia mengatakan bahwa path tersebut tidak hierarkis.
gEdringer
7

Anda perlu memahami jalur di dalam file jar.
Cukup merujuknya secara relatif. Jadi jika Anda memiliki file (myfile.txt), terletak di foo.jar di bawah \src\main\resourcesdirektori (maven style). Anda akan menyebutnya seperti:

src/main/resources/myfile.txt

Jika Anda membuang tabung jar -tvf myjar.jar Anda menggunakan Anda akan melihat output dan jalur relatif di dalam file jar, dan menggunakan FORWARD SLASHES.

eric manley
sumber
Anda memang harus menggunakan garis miring, bahkan di Windows. Itu menyiratkan bahwa Anda tidak dapat menggunakan File.separator.
str
3

Dalam kasus saya, saya menggunakan objek URL, bukan Path.

Mengajukan

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

Sumber daya di classpath menggunakan classloader

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

Ketika saya perlu membaca konten, saya dapat menggunakan kode berikut:

InputStream stream = url.openStream();

Dan Anda dapat mengakses konten menggunakan InputStream.

jmgoyesc
sumber
3

Ini adalah kode yang sama dari pengguna Tombart dengan aliran rata dan dekat untuk menghindari salinan konten file temporer yang tidak lengkap dari sumber jar dan memiliki nama file temp yang unik.

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
sumber
2

File adalah abstraksi untuk file dalam sistem file, dan sistem file tidak tahu apa-apa tentang apa isi dari JAR.

Coba dengan URI, saya pikir ada toples: // protokol yang mungkin berguna untuk tujuan Anda.

fortran
sumber
1

Jalur berikut berhasil bagi saya: classpath:/path/to/resource/in/jar

Anatoly
sumber
1
@Anatoly Bisakah Anda berbagi lebih banyak datails di atas
Kasun Siyambalapitiya
1
private static final String FILE_LOCATION = "com/input/file/somefile.txt";

//Method Body


InputStream invalidCharacterInputStream = URLClassLoader.getSystemResourceAsStream(FILE_LOCATION);

Mendapatkan ini dari getSystemResourceAsStreamadalah pilihan terbaik. Dengan mendapatkan inputstream daripada file atau URL, bekerja di file JAR dan berdiri sendiri.

Shano
sumber
1

Mungkin agak terlambat tetapi Anda dapat menggunakan perpustakaan saya KResourceLoader untuk mendapatkan sumber daya dari toples Anda:

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

Ketika berada di file jar, sumber daya tersebut berada di hierarki paket (bukan hierarki sistem file). Jadi, jika Anda memiliki kelas com.example.Sweet memuat sumber daya bernama "./default.conf" maka nama sumber daya tersebut ditentukan sebagai "/com/example/default.conf".

Tetapi jika itu dalam toples maka itu bukan File ...

Vilnis Krumins
sumber
0

Di dalam folder sumber daya Anda (java / utama / sumber daya) dari jar Anda tambahkan file Anda (kami menganggap bahwa Anda telah menambahkan file xml bernama import.xml ), setelah itu Anda menyuntikkan ResourceLoaderjika Anda menggunakan pegas seperti di bawah ini

@Autowired
private ResourceLoader resourceLoader;

fungsi tur dalam menulis kode di bawah ini untuk memuat file:

    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
sumber
0

Mungkin metode ini dapat digunakan untuk solusi cepat.

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
sumber
Apakah Anda menghilangkan beberapa konteks? Ini memberi saya:java.lang.NullPointerException: Attempt to invoke virtual method 'java.security.CodeSource java.security.ProtectionDomain.getCodeSource()' on a null object reference
Allen Luce
Saya mengedit jawabannya dan menulis seluruh metode yang saya gunakan dalam sebuah perangkat lunak
gbii
0

ikuti kode!

/ 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
sumber