Méthode préférée de chargement des ressources en Java

107

J'aimerais connaître la meilleure façon de charger une ressource en Java:

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

Réponses:

140

Trouvez la solution selon ce que vous voulez ...

Il y a deux choses que getResource/ getResourceAsStream()obtiendra de la classe sur laquelle elle est appelée ...

  1. Le chargeur de classe
  2. Le lieu de départ

Donc si tu fais

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

il tentera de charger foo.txt à partir du même paquet que la classe "this" et avec le chargeur de classe de la classe "this". Si vous mettez un "/" devant, vous faites absolument référence à la ressource.

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

chargera la ressource depuis le chargeur de classe de "this" et depuis le package xyz (il devra être dans le même répertoire que les classes de ce package).

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

se chargera avec le chargeur de classe de contexte mais ne résoudra pas le nom selon aucun package (il doit être absolument référencé)

System.class.getResource(name)

Chargera la ressource avec le chargeur de classe système (il devrait également être absolument référencé, car vous ne pourrez rien mettre dans le package java.lang (le package de System).

Jetez un œil à la source. Indique également que getResourceAsStream appelle simplement "openStream" sur l'URL renvoyée par getResource et la renvoie.

Michael Wiles
la source
Les noms des packages AFAIK n'ont pas d'importance, c'est le chemin de classe du chargeur de classe qui le fait.
Bart van Heukelom
@Bart si vous regardez le code source, vous remarquerez que le nom de la classe est important lorsque vous appelez getResource sur une classe. La première chose que cet appel fait est d'appeler "resolutionName" qui ajoute le préfixe de package si nécessaire. Javadoc pour résoudreName est "Ajouter un préfixe de nom de package si le nom n'est pas absolu Supprimer le début" / "si le nom est absolu"
Michael Wiles
10
Ah, je vois. Absolu signifie ici relatif au chemin de classe, plutôt que absolu du système de fichiers.
Bart van Heukelom
1
Je veux juste ajouter que vous devez toujours vérifier que le flux renvoyé par getResourceAsStream () n'est pas nul, car il le sera si la ressource ne se trouve pas dans le chemin de classe.
stenix
Il convient également de noter que l'utilisation du chargeur de classe de contexte permet de modifier le chargeur de classe lors de l'exécution via Thread#setContextClassLoader. Ceci est utile si vous devez modifier le chemin d'accès aux classes pendant l'exécution du programme.
Max
14

Eh bien, cela dépend en partie de ce que vous voulez qu'il se passe si vous êtes réellement dans une classe dérivée.

Par exemple, supposons que vous soyez SuperClassdans A.jar et SubClassdans B.jar, et que vous exécutiez du code dans une méthode d'instance déclarée dans SuperClassmais où thisfait référence à une instance de SubClass. Si vous l'utilisez, this.getClass().getResource()il semblera relatif à SubClass, dans B.jar. Je soupçonne que ce n'est généralement pas ce qui est requis.

Personnellement, j'utiliserais probablement le Foo.class.getResourceAsStream(name)plus souvent - si vous connaissez déjà le nom de la ressource que vous recherchez, et que vous êtes sûr de son emplacement par rapport à Foo, c'est la façon la plus robuste de le faire IMO.

Bien sûr, il y a des moments où ce n'est pas ce que vous voulez aussi: juger chaque cas selon ses mérites. C'est juste le "Je sais que cette ressource est fournie avec cette classe" est le plus courant que j'ai rencontré.

Jon Skeet
la source
skeet: un doute dans l'instruction "vous exécutez du code dans une méthode d'instance de SuperClass mais où cela fait référence à une instance de SubClass" si nous exécutons des instructions à l'intérieur de la méthode d'instance de superclasse alors "this" fera référence à la superclasse non la sous-classe.
Dead Programmer
1
@Suresh: Non, ce ne sera pas le cas. Essayez-le! Créez deux classes, en faisant dériver l'une de l'autre, puis imprimez la superclasse this.getClass(). Créez une instance de la sous-classe et appelez la méthode ... elle affichera le nom de la sous-classe, pas la superclasse.
Jon Skeet
Merci la méthode d'instance de sous-classe appelle la méthode de superclasse.
Dead Programmer
1
Ce que je me demande, c'est si l'utilisation de this.getResourceAsStream ne pourra charger une ressource qu'à partir du même fichier jar que celui de cette classe et non d'un autre jar. À mon avis, c'est le chargeur de classe qui charge la ressource et ne sera sûrement pas limité du chargement à partir d'un seul fichier jar?
Michael Wiles
10

Je recherche trois endroits comme indiqué ci-dessous. Commentaires bienvenus.

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
la source
Merci, c'est une excellente idée. Juste ce dont j'avais besoin.
devo
2
Je regardais les commentaires dans votre code et le dernier semble intéressant. Toutes les ressources ne sont-elles pas chargées à partir du chemin de classe? Et quels cas le ClassLoader.getSystemResource () couvrirait-il que ce qui précède n'a pas réussi?
nyxz
En toute honnêteté, je ne comprends pas pourquoi vous voudriez charger des fichiers à partir de 3 endroits différents. Vous ne savez pas où vos fichiers sont stockés?
bvdb
3

Je sais qu'il est vraiment tard pour une autre réponse mais je voulais juste partager ce qui m'a aidé à la fin. Il chargera également les ressources / fichiers à partir du chemin absolu du système de fichiers (pas seulement le chemin de classe).

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
la source
0

J'ai essayé de nombreuses méthodes et fonctions suggérées ci-dessus, mais elles n'ont pas fonctionné dans mon projet. Quoi qu'il en soit, j'ai trouvé une solution et la voici:

try {
    InputStream path = this.getClass().getClassLoader().getResourceAsStream("img/left-hand.png");
    img = ImageIO.read(path);
} catch (IOException e) {
    e.printStackTrace();
}
Vladislav
la source
Vous devriez mieux utiliser this.getClass().getResourceAsStream()dans ce cas. Si vous jetez un œil à la source de la getResourceAsStreamméthode, vous remarquerez qu'elle fait la même chose que vous mais de manière plus intelligente (repli si aucun ne ClassLoaderpeut être trouvé sur la classe). Il indique également que vous pouvez rencontrer un potentiel nullsur getClassLoadervotre code ...
Doc Davluz
@PromCompot, comme je l'ai dit, this.getClass().getResourceAsStream()ne fonctionne pas pour moi, donc j'utilise cela fonctionne. Je pense que certaines personnes peuvent faire face à des problèmes comme le mien.
Vladislav