Lendo esse artigo do blog do Daniel, me lembrei que explicar e entender o conceito de defining classloader é bem difícil, principalmente da parte que dita as consequências. Em primeira análise é até simples, toda classe esta associada ao classloader responsável por sua carga, porém a segunda parte que guarda a chave do inferno, o defining classloader (aquele que carregou a classe) limita a visibilidade dela e será sempre o primeiro acionado para resolver os tipos que ela depende.
A regra de escopo que torna classloading um enorme inferno, principalmente pela sutileza que acontece e te morde. De acordo com o Daniel, o classloader acionado não é necessariamente aquele que resolve o tipo, isso acontece devido a delegação. O mecanismo de delegação funciona muito bem quando somente um classloader da hierarquia é capaz de carregar uma dada classe e não existe dependências entre classes no sentido inverso da hierarquia existente entre eles.
Explicando melhor esses dois pontos; primeiro, somente um classloader da hierarquia deve ser capaz de carregar uma dada classe, isso se faz necessário senão acabamos com problemas de esquizofrenia de tipos, quando uma classe é carregada por dois classloaders e passa a existir como dois tipos distintos e gera vários ClassCastException sem razão aparente.
Por exemplo, dado o código a seguir e a existência de dois classloaders: João e José. Imaginemos a situação na qual a tanto José quanto João carregam a classe Exemplo, cada um definindo um tipo. Vamos supor agora que a aplicação chame novaCoisa de uma instância de Exemplo criada a partir do tipo definido pelo classloader João e passe como parâmetro de uma chamada a fazCoisa de uma instância vinda de José. Por mais curioso que seja, o resultado vai ser um ClassCastException e “nome da classe: Exemplo” escrito no console. Legal, não?
class Exemplo{ public void fazCoisa(Object obj) { System.out.println("nome da classe: "+obj.getClass()); Exemplo ex= (Exemplo)obj; ex.toString(); } public Object novaCoisa() { return new Exemplo(); } }
O problema anterior é causado por falta de isolamento, objetos de classloaders no mesmo nível hierárquico jamaisdevem se comunicar, a aplicação deve garantir essa separação. Caso seja realmente necessária esta comunicação, ela deve ser feita via call-by-value, ou seja, serializando os objetos nas bordas.
O outro problema é o do espelho de duas direções, no qual uma classe enxerga a outra mas a recíproca é falsa. Isso ocorre quando uma classe carregada por um classloader mais próximo da raiz depende de uma classe que pode ser carregada apenas por um classloader mais distante. Como exemplo, dada as classes a seguir e dois classloader, Pai e Filho, vamos ver como isso acontece:
class VimDoPai { public void print() { System.out.println(new VimDoFilho()); } } class VimDoFilho { public void print() { System.out.println(new VimDoPai()); } }
Vamos supor que o classloader Filho delega para Pai a carga de classes. Supondo que Filho carregue a classe VimDoFilho e Pai carregue VimDoPai, quando VimDoFilho.print() for chamado, o classloader Filho delega para Pai e consegue uma referencia para a classe VimDoPai, porém quando VimDoPai.print() é chamado, o classloader Pai não encontrará a classe VimDoFilho e um ClassNotFoundError irá acontecer.
No exemplo anterior podemos notar o motivo pelo quando o escopo do classloading é um problema difícil de decifrar. Quando uma classe é carregada por um dado classloader, todas suas dependências serão resolvidas através dele. Tratá-se de uma imposição draconiana, pois abre espaço para uma enorme gama de problemas, como o visto anteriormente. Mais grave é o fato de acabarmos com um sistema no qual objetos que interagem entre sí possuem visões divergentes dos tipos existentes. Infelizmente esta é uma limitação necessária para criar um sistema de tipos sonoro e seguro.
Classloading é um mecanismo muito interessante do Java, pois permite criar coisas fabulosas como o container OSGi, porém não deixa de ser a parte mais dolorosa da plataforma quando ocorrem problemas. Java não possui suporte a realmente isolar um classloader de outro, como outras plataformas possuem, e quando precisamos violar o mecanismo de delegação uma enorme quantidade de problemas podem acontecer.
5 responses so far ↓
1 Juliano D. Carniel (jujo) // May 10, 2007 at 10:55 am
Excelente também!
Havia lido o post do daniel, e o seu fez um ótimo complemento ao dele!
Até
2 Marcos Silva Pereira // May 11, 2007 at 3:19 am
Eu aprendi isso da pior maneira possivel, levando surra do dev loader por achar que uma classe filter não era filter. :-(
valeuz…
3 Daniel Quirino // May 12, 2007 at 3:53 pm
Cara, este lance todo de classloaders me dava arrepios quando eu estava começando com Java EE em 2000. O fato de os containers inverterem a ordem de delegação de class definition me deixou muito confuso no começo e me fazia ter crises de “dia de fúria” quando eu tentava fazer o cast “válido” de uma instância de uma clase vinda de outro class loader para outro tipo definido no meu class loader e obtinha um ClassCastException. Até eu entender o que estava acontecendo, já tinha quase quebrado meu punho socando a mesa.
Aliás, uma coisa que eu acho que não mencionei (ou não deixei bem claro) foi como funciona o esquema de resolução de Simbolic Links, mas que vc matou a pau ai.
4 » Java 6, as APIs de XML, Webservices e classloaders » blog.caelum.com.br // Dec 16, 2007 at 11:44 pm
[...] não usando a terrível variável de ambiente CLASSPATH, acabamos sempre enfrentando o classloader hell. Visite-nos em http://www.caelum.com.br « Conexão Java 2007: Palestras, tutoriais e fotos [...]
5 Classloaders e aplicações isoladas no JBoss // Jun 24, 2008 at 7:48 pm
[...] classloaders e como eles trabalham, pode ler esse excelente post e lapidar o aprendizado com esse feedback muito bom do Rodrigo [...]
Leave a Comment