Diferentes maneiras de carregar um arquivo como um InputStream

216

Qual é a diferença entre:

InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName)

e

InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)

e

InputStream is = this.getClass().getResourceAsStream(fileName)

Quando cada um é mais apropriado do que os outros?

O arquivo que quero ler está no caminho de classe como minha classe que lê o arquivo. Minha classe e o arquivo estão no mesmo jar, empacotados em um arquivo EAR e implementados no WebSphere 6.1.

zqudlyba
fonte

Respostas:

289

Existem diferenças sutis no modo como o que fileNamevocê está passando é interpretado. Basicamente, você tem 2 métodos diferentes: ClassLoader.getResourceAsStream()eClass.getResourceAsStream() . Esses dois métodos localizarão o recurso de maneira diferente.

Em Class.getResourceAsStream(path), o caminho é interpretado como um caminho local para o pacote da classe da qual você está chamando. Por exemplo chamada, String.getResourceAsStream("myfile.txt")irá procurar um arquivo no seu classpath, no seguinte local: "java/lang/myfile.txt". Se o seu caminho começar com a /, será considerado um caminho absoluto e começará a pesquisar a partir da raiz do caminho de classe. Assim, a chamada String.getResourceAsStream("/myfile.txt")examinará o seguinte local no caminho da sua classe ./myfile.txt.

ClassLoader.getResourceAsStream(path)considerará todos os caminhos como absolutos. Então, chamando String.getClassLoader().getResourceAsStream("myfile.txt")e String.getClassLoader().getResourceAsStream("/myfile.txt")ambos procurarão um arquivo no seu caminho de classe no seguinte local:./myfile.txt .

Toda vez que eu menciono um local neste post, ele pode ser um local no seu próprio sistema de arquivos ou dentro do arquivo jar correspondente, dependendo da Class e / ou ClassLoader da qual você está carregando o recurso.

No seu caso, você está carregando a classe de um servidor de aplicativos, portanto, você deve usar em Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)vez de this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()também irá funcionar.

Leia este artigo para obter informações mais detalhadas sobre esse problema específico.


Aviso para usuários do Tomcat 7 e abaixo

Uma das respostas a esta pergunta afirma que minha explicação parece incorreta para o Tomcat 7. Tentei procurar ao redor para ver por que esse seria o caso.

Então, eu olhei o código fonte do Tomcat WebAppClassLoaderpara várias versões do Tomcat. A implementação de findResource(String name)(que é totalmente responsável por produzir a URL para o recurso solicitado) é praticamente idêntica no Tomcat 6 e no Tomcat 7, mas é diferente no Tomcat 8.

Nas versões 6 e 7, a implementação não tenta normalizar o nome do recurso. Isso significa que nessas versões, classLoader.getResourceAsStream("/resource.txt")pode não produzir o mesmo resultado que o classLoader.getResourceAsStream("resource.txt")evento que deveria (desde que o Javadoc especifique). [Código fonte]

Na versão 8, porém, o nome do recurso é normalizado para garantir que a versão absoluta do nome do recurso seja a usada. Portanto, no Tomcat 8, as duas chamadas descritas acima sempre devem retornar o mesmo resultado. [Código fonte]

Como resultado, você deve ter um cuidado extra ao usar ClassLoader.getResourceAsStream()ou Class.getResourceAsStream()nas versões do Tomcat anteriores a 8. E você também deve ter em mente que, class.getResourceAsStream("/resource.txt")na verdade, chama classLoader.getResourceAsStream("resource.txt")(o líder /é removido).

LordOfThePigs
fonte
2
Tenho certeza que getClass().getResourceAsStream("/myfile.txt")se comporta de maneira diferente getClassLoader().getResourceAsStream("/myfile.txt").
Brian Gordon
@BrianGordon: Eles não se comportam de maneira diferente. Na verdade, o javadoc para Class.getResourceAsStream (String) diz o seguinte: "Este método delega para o carregador de classes deste objeto." E fornece várias regras sobre como ele converte um caminho relativo em um caminho absoluto antes de delegar ao diretório carregador de classe.
18893 LordOfThePigs
@LordOfThePigs Veja a fonte real. Class.getResourceAsStream retira a barra principal se você fornecer um caminho absoluto.
Brian Gordon
4
@BrianGordon: O que faz com que ele se comporte exatamente da mesma forma que ClassLoader.getResourceAsStream (), pois o último interpreta todos os caminhos como absolutos, quer eles comecem com uma barra inicial ou não. Portanto, desde que o caminho seja absoluto, os dois métodos se comportam de forma idêntica. Se seu caminho é relativo, então o comportamento é diferente.
LordOfThePigs
Eu não poderia encontrar getClassLoader()de String, é um erro ou precisa de uma extensão?
AaA
21

Use MyClass.class.getClassLoader().getResourceAsStream(path)para carregar recursos associados ao seu código. Use MyClass.class.getResourceAsStream(path)como atalho e para recursos empacotados no pacote da sua turma.

Use Thread.currentThread().getContextClassLoader().getResourceAsStream(path)para obter recursos que fazem parte do código do cliente, não muito restritos ao código de chamada. Você deve ter cuidado com isso, pois o carregador de classes de contexto de encadeamento pode estar apontando para qualquer coisa.

Tom Hawtin - linha de orientação
fonte
6

Java antigo simples no antigo Java 7 simples e nenhuma outra dependência demonstra a diferença ...

Eu coloquei file.txtno c:\temp\e eu coloquei c:\temp\no classpath.

Há apenas um caso em que há uma diferença entre as duas chamadas.

class J {

 public static void main(String[] a) {
    // as "absolute"

    // ok   
    System.err.println(J.class.getResourceAsStream("/file.txt") != null); 

    // pop            
    System.err.println(J.class.getClassLoader().getResourceAsStream("/file.txt") != null); 

    // as relative

    // ok
    System.err.println(J.class.getResourceAsStream("./file.txt") != null); 

    // ok
    System.err.println(J.class.getClassLoader().getResourceAsStream("./file.txt") != null); 

    // no path

    // ok
    System.err.println(J.class.getResourceAsStream("file.txt") != null); 

   // ok
   System.err.println(J.class.getClassLoader().getResourceAsStream("file.txt") != null); 
  }
}
John Lonergan
fonte
muito obrigado, por mim só funcionou 'J.class.getResourceAsStream ("file.txt")'
abbasalim
3

Todas essas respostas por aqui, bem como as respostas desta pergunta , sugerem que o carregamento de URLs absolutos, como "/foo/bar.properties", seja tratado da mesma forma por class.getResourceAsStream(String)e class.getClassLoader().getResourceAsStream(String). Esse não é o caso, pelo menos não na minha configuração / versão do Tomcat (atualmente 7.0.40).

MyClass.class.getResourceAsStream("/foo/bar.properties"); // works!  
MyClass.class.getClassLoader().getResourceAsStream("/foo/bar.properties"); // does NOT work!

Desculpe, não tenho absolutamente nenhuma explicação satisfatória, mas acho que o tomcat faz truques sujos e sua magia negra com os carregadores de classes e causa a diferença. Eu sempre usei class.getResourceAsStream(String)no passado e não tive nenhum problema.

PS: Eu também postei isso aqui

Tim Büthe
fonte
Talvez o tomcat decida não respeitar a especificação e, e não trata todos os caminhos passados ClassLoader.getResourceAsStream()como absolutos? Isso é plausível porque, como mencionado em alguns comentários acima, Class.getResourceAsStreamna verdade chama getClassLoader (). GetResourceAsStream`, mas retira qualquer barra principal.
LordOfThePigs
Depois de verificar no código fonte do Java SE, acho que ter a resposta: ambos Class.getResourceAsStream()e ClassLoader.getResourceAsStream()em última análise, acabam chamando ClassLoader.findResource()que é um método protegido cuja implementação padrão é vazio, mas cuja javadoc afirma explicitamente "implementações carregador de classe deve substituir esse método para especificar onde para encontrar recursos ". Suspeito que a implementação do tomcat desse método específico possa ter falhas.
LordOfThePigs
Eu também comparou a implementação de WebAppClassLoader.findResource(String name)no Tomcat 7 com a do Tomcat 8 , e parece que há uma diferença fundamental. O Tomcat 8 normaliza explicitamente o nome do recurso, adicionando um líder /se ele não contiver nenhum, o que torna todos os nomes absolutos. O Tomcat 7 não. Isso é claramente um erro no Tomcat 7
LordOfThePigs
Eu adicionei um parágrafo sobre isso na minha resposta.
LordOfThePigs
0

Depois de tentar algumas maneiras de carregar o arquivo sem sucesso, lembrei-me de que poderia usá-lo FileInputStream, o que funcionava perfeitamente.

InputStream is = new FileInputStream("file.txt");

Essa é outra maneira de ler um arquivo em um arquivo InputStream: ele lê o arquivo da pasta em execução no momento.

António Almeida
fonte
Não é um arquivo, é um recurso. A resposta não está correta.
Marquês de Lorne
1
@EJP Termino nesta resposta do SO, procurando maneiras de carregar um arquivo, sem saber a diferença entre um arquivo e um recurso. Não vou excluir minha resposta porque isso pode ajudar outras pessoas.
António Almeida
-3

Funciona, tente o seguinte:

InputStream in_s1 =   TopBrandData.class.getResourceAsStream("/assets/TopBrands.xml");
Jaspreet Singh
fonte