Bevorzugte Methode zum Laden von Ressourcen in Java

106

Ich möchte wissen, wie eine Ressource am besten in Java geladen werden kann:

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

Antworten:

140

Erarbeiten Sie die Lösung nach Ihren Wünschen ...

Es gibt zwei Dinge , die getResource/ getResourceAsStream()aus der Klasse erhalten wird er am genannt wird ...

  1. Der Klassenlader
  2. Der Startort

Also wenn du es tust

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

Es wird versucht, foo.txt aus demselben Paket wie die "this" -Klasse und mit dem Klassenladeprogramm der "this" -Klasse zu laden. Wenn Sie ein "/" voranstellen, verweisen Sie absolut auf die Ressource.

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

lädt die Ressource aus dem Klassenlader von "this" und aus dem xyz-Paket (sie muss sich im selben Verzeichnis wie die Klassen in diesem Paket befinden).

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

wird mit dem Kontextklassenlader geladen, löst den Namen jedoch nicht gemäß einem Paket auf (es muss unbedingt referenziert werden)

System.class.getResource(name)

Lädt die Ressource mit dem Systemklassenlader (es müsste auch unbedingt referenziert werden, da Sie nichts in das Paket java.lang (das Paket von System) einfügen können.

Schauen Sie sich einfach die Quelle an. Gibt außerdem an, dass getResourceAsStream nur "openStream" für die von getResource zurückgegebene URL aufruft und diese zurückgibt.

Michael Wiles
quelle
AFAIK-Paketnamen spielen keine Rolle, es ist der Klassenpfad des Klassenladeprogramms.
Bart van Heukelom
@Bart Wenn Sie sich den Quellcode ansehen, werden Sie feststellen, dass der Klassenname eine Rolle spielt, wenn Sie getResource für eine Klasse aufrufen. Der erste Aufruf dieses Aufrufs ist der Aufruf von "resolveName", bei dem gegebenenfalls das Paketpräfix hinzugefügt wird. Javadoc für resolveName lautet "Hinzufügen eines Paketnamenpräfixes, wenn der Name nicht absolut ist Entfernen Sie das führende" / "wenn der Name absolut ist"
Michael Wiles
10
Ah ich sehe. Absolut bedeutet hier relativ zum Klassenpfad und nicht absolut zum Dateisystem.
Bart van Heukelom
1
Ich möchte nur hinzufügen, dass Sie immer überprüfen sollten, ob der von getResourceAsStream () zurückgegebene Stream nicht null ist, da dies der Fall ist, wenn sich die Ressource nicht im Klassenpfad befindet.
Stenix
Erwähnenswert ist auch, dass durch die Verwendung des Kontextklassenladers der Klassenlader zur Laufzeit über geändert werden kann Thread#setContextClassLoader. Dies ist nützlich, wenn Sie den Klassenpfad ändern müssen, während das Programm ausgeführt wird.
Max
14

Nun, es hängt teilweise davon ab, was passieren soll, wenn Sie tatsächlich in einer abgeleiteten Klasse sind.

Angenommen, es SuperClassbefindet sich in A.jar und SubClassin B.jar, und Sie führen Code in einer Instanzmethode aus, die in deklariert ist SuperClass, wobei thissich jedoch auf eine Instanz von bezieht SubClass. Wenn Sie es verwenden this.getClass().getResource(), wird es relativ zu SubClass, in B.jar aussehen. Ich vermute, dass dies normalerweise nicht erforderlich ist.

Persönlich würde ich wahrscheinlich Foo.class.getResourceAsStream(name)am häufigsten verwenden - wenn Sie den Namen der Ressource, nach der Sie suchen, bereits kennen und sicher sind, wo er sich relativ Foobefindet, ist dies die robusteste Methode, dies IMO zu tun.

Natürlich gibt es Zeiten, in denen Sie das auch nicht wollen: Beurteilen Sie jeden Fall nach seinen Vorzügen. Es ist nur die "Ich weiß, dass diese Ressource mit dieser Klasse gebündelt ist" die häufigste, auf die ich gestoßen bin.

Jon Skeet
quelle
skeet: Ein Zweifel in der Anweisung "Sie führen Code in einer Instanzmethode von SuperClass aus, aber wenn sich dies auf eine Instanz von SubClass bezieht", wenn wir Anweisungen innerhalb der Instanzmethode von Super Class ausführen, bezieht sich "this" nicht auf die Oberklasse die Unterklasse.
Toter Programmierer
1
@ Suresh: Nein, wird es nicht. Versuch es! Erstellen Sie zwei Klassen, wobei eine von der anderen abgeleitet wird, und drucken Sie sie dann in der Oberklasse aus this.getClass(). Erstellen Sie eine Instanz der Unterklasse und rufen Sie die Methode auf. Sie gibt den Namen der Unterklasse aus, nicht die Oberklasse.
Jon Skeet
Dank Unterklasse Instanz Methode ruft die Methode der Oberklasse.
Toter Programmierer
1
Ich frage mich, ob die Verwendung von this.getResourceAsStream nur eine Ressource aus demselben Jar laden kann, aus dem diese Klasse stammt, und nicht aus einem anderen Jar. Nach meiner Einschätzung ist es der Klassenlader, der die Ressource lädt und sicher nicht daran gehindert wird, nur aus dem einen Glas zu laden?
Michael Wiles
10

Ich suche drei Orte wie unten gezeigt. Kommentare willkommen.

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
quelle
Danke, das ist eine großartige Idee. Genau das, was ich brauchte.
Devo
2
Ich habe mir die Kommentare in Ihrem Code angesehen und der letzte klingt interessant. Werden nicht alle Ressourcen aus dem Klassenpfad geladen? Und in welchen Fällen würde ClassLoader.getSystemResource () abdecken, dass dies nicht gelungen ist?
Nyxz
Ehrlich gesagt verstehe ich nicht, warum Sie Dateien von 3 verschiedenen Orten laden möchten. Wissen Sie nicht, wo Ihre Dateien gespeichert sind?
Bvdb
3

Ich weiß, dass es für eine weitere Antwort sehr spät ist, aber ich wollte nur mitteilen, was mir am Ende geholfen hat. Es werden auch Ressourcen / Dateien aus dem absoluten Pfad des Dateisystems geladen (nicht nur aus dem Klassenpfad).

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

Ich habe viele Möglichkeiten und Funktionen ausprobiert, die oben vorgeschlagen wurden, aber sie haben in meinem Projekt nicht funktioniert. Jedenfalls habe ich eine Lösung gefunden und hier ist sie:

try {
    InputStream path = this.getClass().getClassLoader().getResourceAsStream("img/left-hand.png");
    img = ImageIO.read(path);
} catch (IOException e) {
    e.printStackTrace();
}
Vladislav
quelle
Sie sollten this.getClass().getResourceAsStream()in diesem Fall besser verwenden . Wenn Sie sich die Quelle der getResourceAsStreamMethode ansehen , werden Sie feststellen, dass sie dasselbe tut wie Sie, jedoch auf intelligentere Weise (Fallback, wenn ClassLoaderin der Klasse keine gefunden werden kann). Es zeigt auch , dass Sie ein Potential begegnen können nullauf getClassLoaderim Code ...
Doc Davluz
@PromCompot funktioniert, wie gesagt, this.getClass().getResourceAsStream()nicht für mich, also benutze ich das. Ich denke, es gibt einige Leute, die mit Problemen wie meinen konfrontiert werden können.
Vladislav