Diferença entre o carregador de classes de contexto do encadeamento e o carregador de classes normal

242

Qual é a diferença entre o carregador de classes de contexto de um encadeamento e um carregador de classes normal?

Ou seja, se Thread.currentThread().getContextClassLoader()e getClass().getClassLoader()retornar objetos de carregador de classe diferentes, qual deles será usado?

abracadabra
fonte

Respostas:

151

Cada classe usará seu próprio carregador de classes para carregar outras classes. Então, se ClassA.classas referências ClassB.class, em seguida, ClassBprecisa estar no caminho de classe do carregador de classe de ClassA, ou de seus pais.

O carregador de classe de contexto de encadeamento é o carregador de classe atual para o encadeamento atual. Um objeto pode ser criado a partir de uma classe ClassLoaderCe depois passado para um thread de propriedade de ClassLoaderD. Nesse caso, o objeto precisa usar Thread.currentThread().getContextClassLoader()diretamente se desejar carregar recursos que não estão disponíveis em seu próprio carregador de classe.

David Roussel
fonte
1
Por que você diz que ClassBdeve estar no caminho de classe do ClassAcarregador (ou ClassAdos pais do carregador)? Não é possível ClassAsubstituir o carregador loadClass(), de forma que ele possa carregar com êxito ClassBmesmo quando ClassBnão estiver no caminho de classe?
Pacerier
8
De fato, nem todos os carregadores de classes têm um caminho de classe. Quando escrevi "A classe B precisa estar no caminho de classe do carregador de classes da ClassA", eu quis dizer "A classe B precisa ser carregável pelo carregador de classes da ClassA". 90% do tempo eles significam o mesmo. Mas se você não estiver usando um carregador de classes baseado em URL, apenas o segundo caso será verdadeiro.
David Roussel
O que significa dizer que " ClassA.classreferências ClassB.class"?
Jameshfisher
1
Quando ClassA possui uma instrução de importação para ClassB, ou se existe um método na ClassA que possui uma variável local do tipo ClassB. Isso fará com que o ClassB seja carregado, se ainda não estiver carregado.
David Roussel
Penso que o meu problema está relacionado com este tópico. O que você acha da minha solução? Eu sei que não é um bom padrão, mas eu não tenho nenhuma outra idéia de como corrigi-lo: stackoverflow.com/questions/29238493/...
Marcin Erbel
109

Isso não responde à pergunta original, mas como a pergunta é altamente classificada e vinculada a qualquer ContextClassLoaderconsulta, acho importante responder à pergunta relacionada de quando o carregador de classes de contexto deve ser usado. Resposta curta: nunca use o carregador de classes de contexto ! Mas defina-o como getClass().getClassLoader()quando você precisar chamar um método que está faltando um ClassLoaderparâmetro.

Quando o código de uma classe pede para carregar outra classe, o carregador de classes correto a ser usado é o mesmo carregador de classe que a classe do chamador (ou seja, getClass().getClassLoader()). É assim que as coisas funcionam 99,9% do tempo, porque é isso que a JVM faz na primeira vez em que você constrói uma instância de uma nova classe, invoca um método estático ou acessa um campo estático.

Quando você deseja criar uma classe usando reflexão (como desserializar ou carregar uma classe nomeada configurável), a biblioteca que faz a reflexão sempre deve perguntar ao aplicativo qual carregador de classes usar, recebendo o ClassLoaderparâmetro como do aplicativo. O aplicativo (que conhece todas as classes que precisam ser construídas) deve ser aprovado getClass().getClassLoader().

Qualquer outra maneira de obter um carregador de classes está incorreta. Se uma biblioteca usos hacks como Thread.getContextClassLoader(), sun.misc.VM.latestUserDefinedLoader()ou sun.reflect.Reflection.getCallerClass()é um erro causado por uma deficiência na API. Basicamente, Thread.getContextClassLoader()existe apenas porque quem projetou a ObjectInputStreamAPI esqueceu de aceitar o ClassLoaderparâmetro, e esse erro assombrou a comunidade Java até hoje.

Dito isto, muitas classes JDK usam um dos poucos hacks para adivinhar o uso de um carregador de classes. Alguns usam o ContextClassLoader(que falha quando você executa aplicativos diferentes em um pool de encadeamentos compartilhados ou quando você sai ContextClassLoader null), outros andam na pilha (que falha quando o chamador direto da classe é uma biblioteca), outros usam o carregador de classes do sistema (o que é bom, desde que esteja documentado para usar apenas classes no CLASSPATH) ou carregador de classes de autoinicialização e alguns usam uma combinação imprevisível das técnicas acima (o que apenas torna as coisas mais confusas). Isso resultou em muito choro e ranger de dentes.

Ao usar essa API, primeiro, tente encontrar uma sobrecarga do método que aceite o carregador de classes como parâmetro . Se não houver um método sensato, tente definir a ContextClassLoaderchamada antes da API (e redefini-la depois):

ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
    // call some API that uses reflection without taking ClassLoader param
} finally {
    Thread.currentThread().setContextClassLoader(originalClassLoader);
}
yonran
fonte
5
Sim, esta é a resposta que eu apontaria para qualquer pessoa que fizesse a pergunta.
Marko Topolnik
6
Essa resposta se concentra no uso do carregador de classes para carregar classes (para instancia-las através de reflexão ou similar), enquanto outro propósito para o qual é usado (e, de fato, o único propósito para o qual eu já o usei pessoalmente) é carregar recursos. O mesmo princípio se aplica ou há situações em que você deseja buscar recursos através do carregador de classes de contexto em vez do carregador de classes de chamadores?
Egor Hans
90

Existe um artigo no javaworld.com que explica a diferença => Qual ClassLoader você deve usar

(1)

Os carregadores de classes de contexto de encadeamento fornecem uma porta traseira em torno do esquema de delegação de carregamento de classes.

Tomemos a JNDI por exemplo: suas tripas são implementadas pelas classes de inicialização no rt.jar (a partir do J2SE 1.3), mas essas classes principais da JNDI podem carregar provedores JNDI implementados por fornecedores independentes e potencialmente implementados no caminho de classe do aplicativo. Esse cenário exige que um carregador de classes pai (o primordial neste caso) carregue uma classe visível para um de seus carregadores de classes filhos (o sistema, por exemplo). A delegação J2SE normal não funciona e a solução alternativa é fazer com que as classes JNDI principais usem carregadores de contexto de encadeamento, efetivamente "encapsulando" a hierarquia do carregador de classes na direção oposta à delegação adequada.

(2) da mesma fonte:

Essa confusão provavelmente ficará com o Java por algum tempo. Pegue qualquer API J2SE com carregamento dinâmico de recursos de qualquer tipo e tente adivinhar qual estratégia de carregamento ela usa. Aqui está uma amostra:

  • JNDI usa classloaders de contexto
  • Class.getResource () e Class.forName () usam o carregador de classe atual
  • JAXP usa classloaders de contexto (a partir do J2SE 1.4)
  • java.util.ResourceBundle usa o carregador de classe atual do chamador
  • Os manipuladores de protocolo de URL especificados através da propriedade do sistema java.protocol.handler.pkgs são consultados apenas nos carregadores de inicialização e no sistema de classes
  • A API de serialização Java usa o carregador de classe atual do chamador por padrão
Timour
fonte
Como é sugerido que a solução alternativa é fazer com que as classes JNDI principais usem carregadores de contexto de encadeamento, não entendi como isso ajuda nesse caso. . Então, como podemos carregá-los usando pai, mesmo se definirmos esse carregador de classes pai no carregador de classes de contexto do encadeamento.
Sunny Gupta
6
@ SAM, a solução sugerida é exatamente o oposto do que você está dizendo no final. Não é o bootstrapcarregador de classes pai que está sendo definido como o carregador de classes de contexto, mas o systemcarregador de classes de caminho de classe filho com o qual Threadestá sendo configurado. As JNDIclasses estão certificando-se de usar Thread.currentThread().getContextClassLoader()para carregar as classes de implementação JNDI disponíveis no caminho de classe.
Ravi Thapliyal
"A delegação J2SE normal não funciona", posso saber por que não funciona? Como o Bootstrap ClassLoader pode carregar apenas a classe do rt.jar e não pode carregar a classe do caminho -class do aplicativo? Certo?
YuFeng Shen
37

Adicionando à resposta do @David Roussel, as classes podem ser carregadas por vários carregadores de classes.

Vamos entender como o carregador de classes funciona.

De javin paul blog em javarevisited:

insira a descrição da imagem aqui

insira a descrição da imagem aqui

ClassLoader segue três princípios.

Princípio da delegação

Uma classe é carregada em Java, quando necessário. Suponha que você tenha uma classe específica de aplicativo chamada Abc.class, a primeira solicitação de carregamento dessa classe chegará ao Application ClassLoader, que delegará ao seu Extension ClassLoader pai, que delegará ainda mais ao carregador de classes Primordial ou Bootstrap

  • O Bootstrap ClassLoader é responsável por carregar os arquivos de classe JDK padrão do rt.jar e é o pai de todos os carregadores de classe em Java. O carregador de classes do Bootstrap não tem nenhum pai.

  • O Extension ClassLoader delega a solicitação de carregamento de classe ao pai, o Bootstrap e, se malsucedido, carrega o formulário da classe diretório jre / lib / ext ou qualquer outro diretório apontado pela propriedade do sistema java.ext.dirs

  • Carregador de classe de sistema ou aplicativo e é responsável por carregar classes específicas do aplicativo a partir da variável de ambiente CLASSPATH, da opção de linha de comando -classpath ou -cp, atributo Class-Path do arquivo Manifest dentro do JAR.

  • Carregador de classe de aplicativo é filho do Extension ClassLoader e é implementado por sun.misc.Launcher$AppClassLoaderclasse.

NOTA: Exceto o carregador de classes Bootstrap , que é implementado na linguagem nativa principalmente em C, todos os carregadores de classes Java são implementados usando java.lang.ClassLoader.

Princípio da visibilidade

De acordo com o princípio da visibilidade, o Child ClassLoader pode ver a classe carregada pelo Parent ClassLoader, mas vice-versa não é verdadeiro.

Princípio da singularidade

De acordo com esse princípio, uma classe carregada pelo Parent não deve ser carregada pelo Child ClassLoader novamente

Ravindra babu
fonte