Modo preferito per caricare le risorse in Java


107

Vorrei conoscere il modo migliore per caricare una risorsa in Java:

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

Risposte:


140

Elabora la soluzione in base a ciò che desideri ...

Ci sono due cose che getResource/ getResourceAsStream()otterrò dalla classe in cui è chiamato ...

  1. Il caricatore di classi
  2. Il luogo di partenza

Quindi se lo fai

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

tenterà di caricare foo.txt dallo stesso pacchetto della classe "this" e con il class loader della classe "this". Se metti una "/" davanti, stai assolutamente facendo riferimento alla risorsa.

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

caricherà la risorsa dal class loader di "this" e dal pacchetto xyz (dovrà essere nella stessa directory delle classi in quel pacchetto).

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

verrà caricato con il caricatore della classe di contesto ma non risolverà il nome in base a nessun pacchetto (deve essere assolutamente referenziato)

System.class.getResource(name)

Caricherà la risorsa con il caricatore di classi di sistema (dovrebbe anche essere assolutamente referenziato, poiché non sarai in grado di mettere nulla nel pacchetto java.lang (il pacchetto di System).

Dai un'occhiata alla fonte. Indica anche che getResourceAsStream chiama semplicemente "openStream" sull'URL restituito da getResource e lo restituisce.


Per quanto ne so, i nomi dei pacchetti non contano, è il classpath del programma di caricamento classi che lo fa.
Bart van Heukelom

@Bart se guardi il codice sorgente noterai che il nome della classe è importante quando chiami getResource su una classe. La prima cosa che fa questa chiamata è chiamare "resolverName" che aggiunge il prefisso del pacchetto se appropriato. Javadoc per resolvedName è "Aggiungi un prefisso del nome del pacchetto se il nome non è assoluto Rimuovi iniziale" / "se il nome è assoluto"
Michael Wiles

10
Ah, capisco. Assoluto qui significa relativo al classpath, piuttosto che assoluto del filesystem.
Bart van Heukelom

1
Voglio solo aggiungere che dovresti sempre controllare che il flusso restituito da getResourceAsStream () non sia nullo, perché lo sarà se la risorsa non è all'interno del classpath.
Stenix

Vale anche la pena notare che l'utilizzo del caricatore di classi di contesto consente di modificare il caricatore di classi in fase di esecuzione tramite Thread#setContextClassLoader. Ciò è utile se è necessario modificare il percorso di classe durante l'esecuzione del programma.
Max

14

Bene, in parte dipende da cosa vuoi che accada se sei effettivamente in una classe derivata.

Ad esempio, supponiamo che SuperClasssia in A.jar e SubClassin B.jar e che tu stia eseguendo codice in un metodo di istanza dichiarato in SuperClassma dove si thisriferisce a un'istanza di SubClass. Se lo usi this.getClass().getResource(), apparirà relativo a SubClass, in B.jar. Sospetto che di solito non sia ciò che è richiesto.

Personalmente probabilmente lo userei Foo.class.getResourceAsStream(name)più spesso: se conosci già il nome della risorsa che stai cercando e sei sicuro di dove è relativo Foo, questo è il modo più robusto per farlo IMO.

Naturalmente ci sono momenti in cui non è quello che vuoi anche tu: giudica ogni caso in base ai suoi meriti. È solo che "So che questa risorsa è in bundle con questa classe" è la più comune in cui mi sono imbattuto.


skeet: un dubbio nell'istruzione "stai eseguendo codice in un metodo di istanza di SuperClass ma dove questo si riferisce a un'istanza di Sottoclasse" se stiamo eseguendo istruzioni all'interno del metodo di istanza di superclasse allora "questo" si riferirà alla superclasse no la sottoclasse.
Programmatore morto il

1
@ Suresh: No, non lo farà. Provalo! Crea due classi, facendo derivare una dall'altra, e poi nella superclasse stampale this.getClass(). Crea un'istanza della sottoclasse e chiama il metodo ... stamperà il nome della sottoclasse, non la superclasse.
Jon Skeet

grazie al metodo di istanza della sottoclasse chiama il metodo della superclasse.
Programmatore morto il

1
Quello che mi chiedo è se l'utilizzo di this.getResourceAsStream sarà solo in grado di caricare una risorsa dallo stesso jar di questa classe e non da un altro jar. Secondo i miei calcoli, è il class loader che carica la risorsa e sicuramente non sarà limitato dal caricare solo da un jar?
Michael Wiles

10

Cerco tre posti come mostrato di seguito. Commenti benvenuti.

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

Grazie, è un'ottima idea. Proprio quello di cui avevo bisogno.
devo

2
Stavo guardando i commenti nel tuo codice e l'ultimo sembra interessante. Non tutte le risorse vengono caricate dal classpath? E quali casi coprirebbe ClassLoader.getSystemResource () se quanto sopra non ha avuto successo?
NYXZ

In tutta onestà, non capisco perché dovresti caricare file da 3 posti diversi. Non sai dove sono archiviati i tuoi file?
bvdb

3

So che è davvero tardi per un'altra risposta, ma volevo solo condividere ciò che mi ha aiutato alla fine. Caricherà anche risorse / file dal percorso assoluto del file system (non solo quello del 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;
    }

}

0

Ho provato molti modi e funzioni che ho suggerito sopra, ma non hanno funzionato nel mio progetto. Comunque ho trovato la soluzione ed eccola qui:

try {
    InputStream path = this.getClass().getClassLoader().getResourceAsStream("img/left-hand.png");
    img = ImageIO.read(path);
} catch (IOException e) {
    e.printStackTrace();
}

Dovresti usare meglio this.getClass().getResourceAsStream()in questo caso. Se dai un'occhiata alla fonte del getResourceAsStreammetodo, noterai che fa la stessa cosa di te ma in un modo più intelligente (fallback se non ClassLoaderpuò essere trovato nella classe). Esso indica inoltre che è possibile incontrare un potenziale nullsu getClassLoadernel codice ...
Doc Davluz

@ PromCompot, come ho detto this.getClass().getResourceAsStream()non funziona per me, quindi lo uso funziona. Penso che ci siano alcune persone che possono affrontare problemi come il mio.
Vladislav
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.