Forma preferida de carregar recursos em Java

107

Gostaria de saber a melhor forma de carregar um recurso em Java:

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

Respostas:

140

Encontre a solução de acordo com o que você deseja ...

Há duas coisas que getResource/ getResourceAsStream()obterá da classe em que é chamado ...

  1. O carregador de classes
  2. O local de partida

Então se você fizer

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

ele tentará carregar foo.txt do mesmo pacote da classe "this" e com o carregador de classes da classe "this". Se você colocar um "/" na frente, estará fazendo uma referência absoluta ao recurso.

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

irá carregar o recurso do carregador de classes de "this" e do pacote xyz (ele precisará estar no mesmo diretório que as classes desse pacote).

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

irá carregar com o carregador de classes de contexto, mas não resolverá o nome de acordo com qualquer pacote (deve ser absolutamente referenciado)

System.class.getResource(name)

Carregará o recurso com o carregador de classes do sistema (também teria que ser absolutamente referenciado, pois você não poderá colocar nada no pacote java.lang (o pacote de System).

Basta dar uma olhada na fonte. Também indica que getResourceAsStream apenas chama "openStream" no URL retornado de getResource e retorna isso.

Michael Wiles
fonte
Os nomes dos pacotes AFAIK não importam, é o classpath do carregador de classes que importa.
Bart van Heukelom,
@Bart se você olhar o código-fonte, notará que o nome da classe importa quando você chama getResource em uma classe. A primeira coisa que essa chamada faz é chamar "resolveName", que adiciona o prefixo do pacote, se apropriado. Javadoc para resolveName é "Adicionar um prefixo de nome de pacote se o nome não for absoluto Remover inicial" / "se o nome for absoluto"
Michael Wiles
10
Ah, entendo. Absoluto aqui significa relativo ao caminho de classe, em vez de absoluto do sistema de arquivos.
Bart van Heukelom,
1
Só quero acrescentar que você deve sempre verificar se o fluxo retornado de getResourceAsStream () não é nulo, porque será se o recurso não estiver no caminho de classe.
stenix,
Também vale a pena observar que usar o carregador de classe de contexto permite que o carregador de classe seja alterado no tempo de execução via Thread#setContextClassLoader. Isso é útil se você precisar modificar o caminho de classe enquanto o programa está sendo executado.
Máx.
14

Bem, isso depende parcialmente do que você deseja que aconteça se estiver realmente em uma classe derivada.

Por exemplo, suponha que SuperClassestá em A.jar e SubClassem B.jar, e você está executando o código em um método de instância declarado em, SuperClassmas onde thisse refere a uma instância de SubClass. Se você usá- this.getClass().getResource()lo, terá uma aparência relativa a SubClass, em B.jar. Eu suspeito que geralmente não é o que é necessário.

Pessoalmente, eu provavelmente usaria com Foo.class.getResourceAsStream(name)mais frequência - se você já sabe o nome do recurso que está procurando e tem certeza de onde ele está relacionado Foo, essa é a maneira mais robusta de fazê-lo IMO.

É claro que há momentos em que você também não quer isso: julgue cada caso pelos seus méritos. É apenas o "Eu sei que este recurso vem junto com esta classe" é o mais comum que encontrei.

Jon Skeet
fonte
skeet: uma dúvida na instrução "você está executando o código em um método de instância da SuperClasse, mas onde isso se refere a uma instância da SubClasse" se estivermos executando instruções dentro do método de instância da superclasse, então "isso" se referirá à superclasse não a subclasse.
Programador morto,
1
@Suresh: Não, não vai. Tente! Crie duas classes, fazendo uma derivar da outra, e depois imprima na superclasse this.getClass(). Crie uma instância da subclasse e chame o método ... ele imprimirá o nome da subclasse, não da superclasse.
Jon Skeet,
graças ao método da instância da subclasse chama o método da superclasse.
Programador morto em
1
O que estou pensando é se o uso de this.getResourceAsStream só será capaz de carregar um recurso do mesmo jar de onde veio este clas e não de outro jar. Pelas minhas contas, é o carregador de classes que carrega o recurso e certamente não será impedido de carregar apenas de um jar?
Michael Wiles,
10

Eu procuro três lugares como mostrado abaixo. Comentários são bem-vindos.

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
fonte
Obrigado, esta é uma ótima ideia. Exatamente o que eu precisava.
devo
2
Eu estava olhando os comentários em seu código e o último parece interessante. Todos os recursos não são carregados do classpath? E quais casos o ClassLoader.getSystemResource () cobriria em que o acima não funcionou?
Nyxz
Com toda a honestidade, não entendo por que você deseja carregar arquivos de 3 lugares diferentes. Você não sabe onde seus arquivos estão armazenados?
bvdb
3

Eu sei que é muito tarde para outra resposta, mas eu só queria compartilhar o que me ajudou no final. Ele também carregará recursos / arquivos do caminho absoluto do sistema de arquivos (não apenas do caminho 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
fonte
0

Tentei várias maneiras e funções sugeridas acima, mas não funcionaram no meu projeto. De qualquer forma, encontrei a solução e aqui está:

try {
    InputStream path = this.getClass().getClassLoader().getResourceAsStream("img/left-hand.png");
    img = ImageIO.read(path);
} catch (IOException e) {
    e.printStackTrace();
}
Vladislav
fonte
Você deveria usar melhor this.getClass().getResourceAsStream() neste caso. Se você getResourceAsStreamder uma olhada na fonte do método, notará que ele faz a mesma coisa que você, mas de uma forma mais inteligente (fallback se nenhum ClassLoaderfor encontrado na classe). Ele também indica que você pode encontrar um potencial nullem getClassLoaderem seu código ...
Doc Davluz
@PromCompot, como eu disse this.getClass().getResourceAsStream()não está funcionando para mim, então eu uso isso funciona. Acho que existem algumas pessoas que podem enfrentar problemas como o meu.
Vladislav