Qual é a diferença entre Class.getResource () e ClassLoader.getResource ()?

194

Gostaria de saber qual é a diferença entre Class.getResource()e ClassLoader.getResource()?

editar: Quero saber especialmente se há armazenamento em cache no nível do arquivo / diretório. Como em "as listagens de diretório são armazenadas em cache na versão da classe?"

AFAIK o seguinte deve essencialmente fazer o mesmo, mas não é:

getClass().getResource() 
getClass().getClassLoader().getResource()

Descobri isso ao mexer em algum código de geração de relatório que cria um novo arquivo a WEB-INF/classes/partir de um arquivo existente nesse diretório. Ao usar o método de Class, pude encontrar arquivos que estavam lá na implantação usando getClass().getResource(), mas ao tentar buscar o arquivo recém-criado, recebi um objeto nulo. Navegar no diretório mostra claramente que o novo arquivo está lá. Os nomes dos arquivos foram anexados com uma barra como em "/myFile.txt".

A ClassLoaderversão do, getResource()por outro lado, encontrou o arquivo gerado. A partir dessa experiência, parece que há algum tipo de cache da listagem de diretórios em andamento. Estou certo e, em caso afirmativo, onde isso está documentado?

Nos documentos da API emClass.getResource()

Localiza um recurso com um determinado nome. As regras para pesquisar recursos associados a uma determinada classe são implementadas pelo carregador de classes que define a classe. Este método delega para o carregador de classes deste objeto. Se este objeto foi carregado pelo carregador de classes de autoinicialização, o método delega para ClassLoader.getSystemResource (java.lang.String).

Para mim, diz "Class.getResource está realmente chamando getResource () do seu próprio carregador de classe". O que seria o mesmo que fazer getClass().getClassLoader().getResource(). Mas obviamente não é. Alguém poderia me fornecer alguma iluminação sobre este assunto?

oligofren
fonte

Respostas:

6

Para responder à pergunta se existe algum cache em andamento.

Investiguei esse ponto ainda mais executando um aplicativo Java independente que carregava continuamente um arquivo do disco usando o método getResourceAsStream ClassLoader. Consegui editar o arquivo e as alterações foram refletidas imediatamente, ou seja, o arquivo foi recarregado do disco sem armazenar em cache.

No entanto: estou trabalhando em um projeto com vários módulos e projetos da Web que dependem um do outro. Estou usando o IntelliJ como meu IDE para compilar e executar os projetos da web.

Percebi que as opções acima pareciam não mais ser verdadeiras, o motivo é que o arquivo que eu estava sendo carregado agora está inserido em uma jarra e implantado no projeto da Web em questão. Eu só notei isso depois de tentar alterar o arquivo na minha pasta de destino, sem sucesso. Isso fez parecer que havia um cache em andamento.

mchlstckl
fonte
Eu também usei o Maven e o IntelliJ, então essa é a resposta em um ambiente que mais se aproxima do meu e tem uma explicação razoável para a pergunta nº 2.
precisa saber é
249

Class.getResourcepode levar um nome de recurso "relativo", que é tratado em relação ao pacote da classe. Como alternativa, você pode especificar um nome de recurso "absoluto" usando uma barra invertida. Os caminhos de recurso do carregador de classes são sempre considerados absolutos.

Portanto, o seguinte é basicamente equivalente:

foo.bar.Baz.class.getResource("xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("foo/bar/xyz.txt");

E também são esses (mas são diferentes do acima):

foo.bar.Baz.class.getResource("/data/xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("data/xyz.txt");
Jon Skeet
fonte
Boa resposta com exemplos claros. Embora o post realmente tenha como objetivo obter respostas para duas perguntas, agora vejo que a segunda pergunta está meio oculta. Bastante certeza de como / se eu deveria atualizar o post para refletir isso, mas o que eu gostaria de saber segundo é isso (próximo comentário):
oligofren
2
Existe algum tipo de cache na versão Class.getResource ()? O que me levou a acreditar que isso é a geração de alguns relatórios de jasper: Usamos getClass (). GetResource ("/ aDocument.jrxml") para buscar o arquivo xml de jasper. Um arquivo jasper binário é produzido no mesmo diretório. getClass (). getResource ("/ aDocument.jasper") não pode encontrá-lo, embora possa encontrar claramente documentos no mesmo nível (o arquivo de entrada). É aqui que ClassLoader.getResource () se mostrou útil, pois parece que não usa o cache da lista de diretórios. Mas não consigo encontrar documentação sobre isso.
Oligofren
2
@oligofren: Hmm ... Eu não iria esperar Class.getResource () para fazer qualquer caching lá ...
Jon Skeet
1
@JonSkeet por que this.getClass().getClassLoader().getResource("/");retornar nulo? Não deve ser o mesmo quethis.getClass().getClassLoader().getResource(".");
Asif Mushtaq 16/02
@ Desconhecido: Eu acho que você provavelmente deveria fazer uma nova pergunta sobre isso.
21718 Jon Skeet
22

A primeira chamada pesquisa em relação ao .classarquivo enquanto a última pesquisa em relação à raiz do caminho de classe.

Para depurar problemas como esse, imprimo o URL:

System.out.println( getClass().getResource(getClass().getSimpleName() + ".class") );
Aaron Digulla
fonte
3
Eu acho que o "root do classloader" seria mais preciso que o "classpath root" - só para ser exigente.
Jon Skeet
4
Ambos podem procurar "caminhos absolutos" se o nome do arquivo é prefixado por "/"
oligofren
2
Interessante ... Estou atingindo um caso em que getClass (). GetResource ("/ someAbsPath") retorna uma URL no tipo /path/to/mylib.jar!/someAbsPath e getClass (). GetClassLoafer (). GetResource (" / someAbsPath ") return null ... So 'raiz do carregador de classe' parece não ser uma noção muito bem definida ...
Pierre Henry
8
@PierreHenry: getClassLoader().getResource("/...")sempre retorna null- o carregador de classes não remove a liderança /do caminho, portanto, a pesquisa sempre falha. getClass().getResource()Manipula apenas uma partida /como um caminho absoluto em relação ao caminho de classe.
Aaron Digulla
17

Tinha que procurar nas especificações:

A documentação getResource () da classe indica a diferença:

Este método delega a chamada para o carregador de classes, após fazer essas alterações no nome do recurso: se o nome do recurso começar com "/", ele não será alterado; caso contrário, o nome do pacote será anexado ao nome do recurso após a conversão de "." para "/". Se este objeto foi carregado pelo carregador de autoinicialização, a chamada é delegada para ClassLoader.getSystemResource.

Bernd Elkemann
fonte
2
Você tem alguma informação sobre se ele também armazena em cache a listagem de diretórios? Essa foi a principal diferença entre os dois métodos ao procurar um arquivo de entrada e depois criar um arquivo usando o mesmo diretório. A versão da classe não a encontrou, a versão do ClassLoader (ambas usando "/file.txt").
11117 oligofren
11

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
Este comportamento parece ser um bug no Tomcat que foi corrigido na versão 8. Eu adicionado um parágrafo sobre isso na minha resposta sobre esta questão
LordOfThePigs
2

Class.getResourcesrecuperaria o recurso pelo carregador de classes que carrega o objeto. While ClassLoader.getResourcerecuperaria o recurso usando o carregador de classe especificado.

lwpro2
fonte
0

Eu tentei ler a partir do input1.txt que estava dentro de um dos meus pacotes junto com a classe que estava tentando lê-lo.

Os seguintes trabalhos:

String fileName = FileTransferClient.class.getResource("input1.txt").getPath();

System.out.println(fileName);

BufferedReader bufferedTextIn = new BufferedReader(new FileReader(fileName));

A parte mais importante era chamar getPath()se você deseja o nome do caminho correto no formato String. NÃO USE,toString() pois ele adicionará algum texto de formatação extra que TOTALMENTE ENCONTRARÁ com o nome do arquivo (você pode experimentá-lo e ver a impressão).

Passou 2 horas depurando isso ... :(

Kevin Lee
fonte
Recursos não são arquivos. Eles podem não ser descompactados do arquivo JAR ou WAR e, se não forem, FileReaderou FileInputStreamnão podem ser usados ​​para acessá-los. A resposta não está correta.
Marquês de Lorne