Berbagai cara memuat file sebagai InputStream

216

Apa perbedaan antara:

InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName)

dan

InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)

dan

InputStream is = this.getClass().getResourceAsStream(fileName)

Kapan masing-masing lebih tepat untuk digunakan daripada yang lain?

File yang ingin saya baca ada di classpath sebagai kelas saya yang membaca file. Kelas saya dan file berada dalam tabung yang sama dan dikemas dalam file EAR, dan digunakan di WebSphere 6.1.

zqudlyba
sumber

Jawaban:

289

Ada perbedaan halus tentang bagaimana fileNameAnda menyampaikan ditafsirkan. Pada dasarnya, Anda memiliki 2 metode berbeda: ClassLoader.getResourceAsStream()dan Class.getResourceAsStream(). Kedua metode ini akan menemukan sumber daya secara berbeda.

Di Class.getResourceAsStream(path), path ditafsirkan sebagai path lokal ke paket dari kelas yang Anda panggil. Misalnya panggilan, String.getResourceAsStream("myfile.txt")akan mencari file di classpath Anda di lokasi berikut: "java/lang/myfile.txt". Jika path Anda dimulai dengan a /, maka itu akan dianggap sebagai path absolut, dan akan mulai mencari dari akar classpath. Jadi panggilan String.getResourceAsStream("/myfile.txt")akan melihat lokasi berikut di jalur kelas Anda ./myfile.txt.

ClassLoader.getResourceAsStream(path)akan menganggap semua jalur sebagai jalur absolut. Jadi menelepon String.getClassLoader().getResourceAsStream("myfile.txt")dan String.getClassLoader().getResourceAsStream("/myfile.txt")akan baik mencari file di classpath Anda di lokasi berikut: ./myfile.txt.

Setiap kali saya menyebutkan lokasi dalam posting ini, itu bisa menjadi lokasi di sistem file Anda sendiri, atau di dalam file jar yang sesuai, tergantung pada Kelas dan / atau ClassLoader tempat Anda memuat sumber daya.

Dalam kasus Anda, Anda memuat kelas dari Application Server, sehingga Anda harus menggunakan Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)bukan this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()juga akan bekerja.

Baca artikel ini untuk informasi lebih rinci tentang masalah khusus itu.


Peringatan untuk pengguna Tomcat 7 dan di bawah

Salah satu jawaban untuk pertanyaan ini menyatakan bahwa penjelasan saya tampaknya tidak benar untuk Tomcat 7. Saya sudah mencoba melihat-lihat untuk melihat mengapa itu akan terjadi.

Jadi saya telah melihat kode sumber Tomcat WebAppClassLoaderuntuk beberapa versi Tomcat. Implementasi findResource(String name)(yang sepenuhnya bertanggung jawab untuk menghasilkan URL ke sumber daya yang diminta) sebenarnya identik dalam Tomcat 6 dan Tomcat 7, tetapi berbeda dalam Tomcat 8.

Dalam versi 6 dan 7, implementasi tidak berusaha untuk menormalkan nama sumber. Ini berarti bahwa dalam versi-versi ini, classLoader.getResourceAsStream("/resource.txt")mungkin tidak menghasilkan hasil yang sama seperti classLoader.getResourceAsStream("resource.txt")peristiwa meskipun seharusnya (karena apa yang ditentukan oleh Javadoc). [Kode sumber]

Namun dalam versi 8, nama sumber dinormalisasi untuk menjamin bahwa versi absolut dari nama sumber adalah yang digunakan. Oleh karena itu, dalam Tomcat 8, dua panggilan yang dijelaskan di atas harus selalu mengembalikan hasil yang sama. [Kode sumber]

Akibatnya, Anda harus ekstra hati-hati saat menggunakan ClassLoader.getResourceAsStream()atau Class.getResourceAsStream()pada versi Tomcat lebih awal dari 8. Dan Anda juga harus ingat bahwa class.getResourceAsStream("/resource.txt")panggilan sebenarnya classLoader.getResourceAsStream("resource.txt")(pemimpin /dilucuti).

LordOfThePigs
sumber
2
Saya cukup yakin itu getClass().getResourceAsStream("/myfile.txt")berperilaku berbeda dari getClassLoader().getResourceAsStream("/myfile.txt").
Brian Gordon
@BrianGordon: Mereka tidak berperilaku berbeda. Sebenarnya, javadoc untuk Class.getResourceAsStream (String) mengatakan hal berikut: "Metode ini mendelegasikan ke pemuat kelas objek ini.", Dan kemudian memberikan banyak aturan tentang cara mengubah jalur relatif ke jalur absolut sebelum mendelegasikan ke classloader.
LordOfThePigs
@LordOfThePigs Lihatlah sumber yang sebenarnya. Class.getResourceAsStream menghapus strip maju terkemuka jika Anda memberikan jalur absolut.
Brian Gordon
4
@BrianGordon: Yang membuatnya berperilaku persis sama dengan ClassLoader.getResourceAsStream () karena yang terakhir menafsirkan semua jalur sebagai absolut, apakah mereka mulai dengan garis miring atau tidak. Jadi selama Anda jalan mutlak, kedua metode berperilaku identik. Jika jalur Anda relatif, maka perilakunya berbeda.
LordOfThePigs
Saya tidak bisa menemukan getClassLoader()dari String, apakah itu kesalahan atau butuh perpanjangan?
AaA
21

Gunakan MyClass.class.getClassLoader().getResourceAsStream(path)untuk memuat sumber daya yang terkait dengan kode Anda. Gunakan MyClass.class.getResourceAsStream(path)sebagai jalan pintas, dan untuk sumber daya yang dikemas dalam paket kelas Anda.

Gunakan Thread.currentThread().getContextClassLoader().getResourceAsStream(path)untuk mendapatkan sumber daya yang merupakan bagian dari kode klien, tidak terikat dengan ketat pada kode panggilan. Anda harus berhati-hati dengan ini karena loader kelas konteks thread dapat menunjuk pada apa saja.

Tom Hawtin - tackline
sumber
6

Java tua polos di Java 7 polos dan tidak ada dependensi lain yang menunjukkan perbedaan ...

Saya dimasukkan ke file.txtdalam c:\temp\dan saya memakai c:\temp\classpath.

Hanya ada satu kasus di mana ada perbedaan antara keduanya.

class J {

 public static void main(String[] a) {
    // as "absolute"

    // ok   
    System.err.println(J.class.getResourceAsStream("/file.txt") != null); 

    // pop            
    System.err.println(J.class.getClassLoader().getResourceAsStream("/file.txt") != null); 

    // as relative

    // ok
    System.err.println(J.class.getResourceAsStream("./file.txt") != null); 

    // ok
    System.err.println(J.class.getClassLoader().getResourceAsStream("./file.txt") != null); 

    // no path

    // ok
    System.err.println(J.class.getResourceAsStream("file.txt") != null); 

   // ok
   System.err.println(J.class.getClassLoader().getResourceAsStream("file.txt") != null); 
  }
}
John Lonergan
sumber
sangat terima kasih, bagi saya hanya bekerja 'J.class.getResourceAsStream ("file.txt")'
abbasalim
3

Semua jawaban di sekitar sini, serta jawaban dalam pertanyaan ini , menyarankan bahwa memuat URL absolut, seperti "/foo/bar.properties" diperlakukan sama oleh class.getResourceAsStream(String)dan class.getClassLoader().getResourceAsStream(String). Ini BUKAN kasusnya, setidaknya tidak dalam konfigurasi / versi Tomcat saya (saat ini 7.0.40).

MyClass.class.getResourceAsStream("/foo/bar.properties"); // works!  
MyClass.class.getClassLoader().getResourceAsStream("/foo/bar.properties"); // does NOT work!

Maaf, saya sama sekali tidak memiliki penjelasan yang memuaskan, tapi saya kira kucing jantan melakukan trik kotor dan sihir hitamnya dengan classloader dan menyebabkan perbedaan. Saya selalu menggunakan class.getResourceAsStream(String)di masa lalu dan tidak punya masalah.

PS: Saya juga memposting ini di sini

Tim Büthe
sumber
Mungkin kucing jantan memutuskan untuk tidak menghormati spesifikasi dan, dan tidak memperlakukan semua jalur yang dilalui ClassLoader.getResourceAsStream()sebagai mutlak? Ini masuk akal karena seperti yang disebutkan dalam beberapa komentar di atas, Class.getResourceAsStreamsebenarnya memanggil getClassLoader (). GetResourceAsStream` tetapi menghapus semua garis miring utama.
LordOfThePigs
Setelah memeriksa dalam kode sumber Java SE, saya pikir saya memegang jawabannya: Keduanya Class.getResourceAsStream()dan ClassLoader.getResourceAsStream()akhirnya memanggil ClassLoader.findResource()yang merupakan metode yang dilindungi yang implementasi standarnya kosong, tetapi yang javadoc secara eksplisit menyatakan "Implementasi loader kelas harus mengganti metode ini untuk menentukan di mana untuk menemukan sumber daya ". Saya menduga implementasi tomcat dari metode khusus ini mungkin cacat.
LordOfThePigs
Saya juga membandingkan implementasi WebAppClassLoader.findResource(String name)di Tomcat 7 dengan Tomcat 8 , dan tampaknya ada perbedaan utama. Tomcat 8 secara eksplisit menormalkan nama sumber daya dengan menambahkan sebuah arahan /jika tidak mengandung apa pun, yang membuat semua nama mutlak. Tomcat 7 tidak. Itu jelas bug di Tomcat 7
LordOfThePigs
Saya menambahkan paragraf tentang itu dalam jawaban saya.
LordOfThePigs
0

Setelah mencoba beberapa cara untuk memuat file tanpa hasil, saya ingat saya dapat menggunakannya FileInputStream, yang bekerja dengan sempurna.

InputStream is = new FileInputStream("file.txt");

Ini adalah cara lain untuk membaca file menjadi InputStream, membaca file dari folder yang sedang berjalan.

António Almeida
sumber
Ini bukan file, ini adalah sumber daya. Jawabannya tidak benar.
Marquis of Lorne
1
@ EJP Saya akhirnya dalam jawaban SO ini, mencari cara untuk memuat file, tanpa mengetahui perbedaan antara file dan sumber daya. Saya tidak akan menghapus jawaban saya karena dapat membantu orang lain.
António Almeida
-3

Berhasil, coba ini:

InputStream in_s1 =   TopBrandData.class.getResourceAsStream("/assets/TopBrands.xml");
Jaspreet Singh
sumber