Cara yang disukai untuk memuat sumber daya di Java

107

Saya ingin tahu cara terbaik memuat sumber daya di Java:

  • this.getClass().getResource() (or getResourceAsStream()),
  • Thread.currentThread().getContextClassLoader().getResource(name),
  • System.class.getResource(name).
Doc Davluz
sumber

Jawaban:

140

Cari solusinya sesuai dengan yang Anda inginkan ...

Ada dua hal yang akan getResource/ getResourceAsStream()dapatkan dari kelas tempat dipanggilnya ...

  1. Pemuat kelas
  2. Lokasi awal

Jadi jika Anda melakukannya

this.getClass().getResource("foo.txt");

ini akan mencoba memuat foo.txt dari paket yang sama dengan kelas "this" dan dengan pemuat kelas dari kelas "this". Jika Anda meletakkan "/" di depan maka Anda benar-benar mereferensikan sumber daya.

this.getClass().getResource("/x/y/z/foo.txt")

akan memuat sumber daya dari pemuat kelas "this" dan dari paket xyz (ini harus berada di direktori yang sama dengan kelas dalam paket itu).

Thread.currentThread().getContextClassLoader().getResource(name)

akan memuat dengan pemuat kelas konteks tetapi tidak akan menyelesaikan nama sesuai dengan paket apa pun (itu harus benar-benar direferensikan)

System.class.getResource(name)

Akan memuat sumber daya dengan pemuat kelas sistem (itu harus benar-benar direferensikan juga, karena Anda tidak akan dapat memasukkan apa pun ke dalam paket java.lang (paket Sistem).

Lihat saja sumbernya. Juga menunjukkan bahwa getResourceAsStream hanya memanggil "openStream" pada URL yang ditampilkan dari getResource dan menampilkannya.

Michael Wiles
sumber
Nama paket AFAIK tidak penting, classpath dari class loader yang penting.
Bart van Heukelom
@Bart jika Anda melihat kode sumber, Anda akan melihat bahwa nama kelas itu penting saat Anda memanggil getResource di kelas. Hal pertama yang dilakukan panggilan ini adalah memanggil "ResolveName" yang menambahkan prefiks paket jika sesuai. Javadoc untuk resolName adalah "Tambahkan awalan nama paket jika nama tidak mutlak Hapus awalan" / "jika nama mutlak"
Michael Wiles
10
Ah, begitu. Absolut di sini berarti relatif terhadap classpath, bukan absolut sistem file.
Bart van Heukelom
1
Saya hanya ingin menambahkan bahwa Anda harus selalu memeriksa bahwa aliran yang dikembalikan dari getResourceAsStream () tidak null, karena akan menjadi jika sumber daya tidak dalam classpath.
stenix
Yang juga perlu diperhatikan adalah bahwa menggunakan pemuat kelas konteks memungkinkan pemuat kelas diubah pada waktu proses melalui Thread#setContextClassLoader. Ini berguna jika Anda perlu mengubah jalur kelas saat program sedang dijalankan.
Maks
14

Yah, itu sebagian tergantung pada apa yang Anda inginkan terjadi jika Anda benar - benar berada di kelas turunan.

Misalnya, anggap SuperClassdalam A.jar dan SubClassdalam B.jar, dan Anda menjalankan kode dalam metode instance yang dideklarasikan SuperClasstetapi di mana thismerujuk ke instance SubClass. Jika Anda menggunakannya this.getClass().getResource()akan terlihat relatif SubClass, di B.jar. Saya rasa biasanya bukan itu yang diminta.

Secara pribadi saya mungkin Foo.class.getResourceAsStream(name)paling sering menggunakan - jika Anda sudah tahu nama sumber daya yang Anda cari, dan Anda yakin di mana itu relatif Foo, itulah cara paling kuat untuk melakukannya IMO.

Tentu saja ada kalanya Anda juga tidak menginginkannya: menilai setiap kasus berdasarkan manfaatnya. Hanya "Saya tahu sumber daya ini digabungkan dengan kelas ini" adalah yang paling umum yang pernah saya temui.

Jon Skeet
sumber
skeet: keraguan dalam pernyataan "Anda menjalankan kode dalam metode instance SuperClass tetapi di mana ini merujuk ke instance SubClass" jika kita menjalankan pernyataan di dalam metode instance kelas super maka "ini" akan merujuk ke superclass bukan subclass.
Dead Programmer
1
@ Suresh: Tidak, itu tidak akan. Cobalah! Buat dua kelas, buat satu turunan dari yang lain, dan kemudian di cetakan kelas super this.getClass(). Buat sebuah instance dari subclass dan panggil metode ... itu akan mencetak nama subclass tersebut, bukan superclassnya.
Jon Skeet
terima kasih metode instance subclass memanggil metode superclass.
Dead Programmer
1
Yang saya ingin tahu adalah apakah menggunakan this.getResourceAsStream hanya akan dapat memuat sumber daya dari tabung yang sama dengan klas ini dari dan bukan dari tabung lain. Menurut perhitungan saya, ini adalah pemuat kelas yang memuat sumber daya dan tentunya tidak akan dibatasi dari memuat hanya dari satu botol?
Michael Wiles
10

Saya mencari tiga tempat seperti yang ditunjukkan di bawah ini. Komentar diterima.

public URL getResource(String resource){

    URL url ;

    //Try with the Thread Context Loader. 
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    if(classLoader != null){
        url = classLoader.getResource(resource);
        if(url != null){
            return url;
        }
    }

    //Let's now try with the classloader that loaded this class.
    classLoader = Loader.class.getClassLoader();
    if(classLoader != null){
        url = classLoader.getResource(resource);
        if(url != null){
            return url;
        }
    }

    //Last ditch attempt. Get the resource from the classpath.
    return ClassLoader.getSystemResource(resource);
}
dogbane
sumber
Terima kasih, ini ide yang bagus. Hanya apa yang saya butuhkan.
devo
2
Saya melihat komentar di kode Anda dan yang terakhir terdengar menarik. Bukankah semua resource dimuat dari classpath? Dan kasus apa yang akan ditutupi oleh ClassLoader.getSystemResource () yang tidak berhasil di atas?
nyxz
Sejujurnya, saya tidak mengerti mengapa Anda ingin memuat file dari 3 tempat berbeda. Apakah Anda tidak tahu di mana file Anda disimpan?
bvdb
3

Saya tahu ini sangat terlambat untuk jawaban lain tetapi saya hanya ingin berbagi apa yang membantu saya pada akhirnya. Ini juga akan memuat resource / file dari jalur absolut sistem file (tidak hanya classpath).

public class ResourceLoader {

    public static URL getResource(String resource) {
        final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
        classLoaders.add(Thread.currentThread().getContextClassLoader());
        classLoaders.add(ResourceLoader.class.getClassLoader());

        for (ClassLoader classLoader : classLoaders) {
            final URL url = getResourceWith(classLoader, resource);
            if (url != null) {
                return url;
            }
        }

        final URL systemResource = ClassLoader.getSystemResource(resource);
        if (systemResource != null) {
            return systemResource;
        } else {
            try {
                return new File(resource).toURI().toURL();
            } catch (MalformedURLException e) {
                return null;
            }
        }
    }

    private static URL getResourceWith(ClassLoader classLoader, String resource) {
        if (classLoader != null) {
            return classLoader.getResource(resource);
        }
        return null;
    }

}
nyxz
sumber
0

Saya mencoba banyak cara dan fungsi yang disarankan di atas, tetapi tidak berhasil dalam proyek saya. Pokoknya saya sudah menemukan solusinya dan ini dia:

try {
    InputStream path = this.getClass().getClassLoader().getResourceAsStream("img/left-hand.png");
    img = ImageIO.read(path);
} catch (IOException e) {
    e.printStackTrace();
}
Vladislav
sumber
Sebaiknya gunakan this.getClass().getResourceAsStream()dalam kasus ini. Jika Anda melihat sumber getResourceAsStreammetode, Anda akan melihat bahwa ia melakukan hal yang sama dari Anda tetapi dengan cara yang lebih cerdas (fallback jika tidak ada yang ClassLoaderdapat ditemukan di kelas). Ini juga menunjukkan bahwa Anda dapat menemukan potensi nulldi getClassLoaderdalam kode Anda…
Doc Davluz
@PromCompot, seperti yang saya katakan this.getClass().getResourceAsStream()tidak berfungsi untuk saya, jadi saya menggunakan yang berfungsi. Saya pikir ada beberapa orang yang bisa menghadapi masalah seperti saya.
Vladislav