Outro dia um colega estava fritando a cabeça com um problema que, segundo ele, não deveria estar acontecendo. “Cara, eu juro que o deploy foi feito corretamente. Corto meu braço fora se eu não tiver feito o deploy do EAR corretamente!”. Ok, acredito nele, mesmo porque ele mandou o log do appserver dele e nada de errado, aparentemente. Contudo, vendo o log da sua aplicação, pude perceber que havia uma série de linkage errors (aka java.lang.NoClassDefFoundError) quando ele tentava acessar alguns EJBs que não residiam na mesma. Certo, a vida ensina e os erros mais ainda, principalmente quando você tem um stacktrace
. Então resolvi quebrar o galho deste meu colega e ensiná-lo algo com a qual ele nunca precisou se preocupar tanto, que é como Java faz para carregar classes.
Ele nunca provavelmente precisou (até então) se preocupar com carregamento de classes e você pode estar se perguntando porque você também deveria se preocupar com isso sendo que você tem meu email ou o GUJ e pode pedir ajuda quando precisar
. A melhor (e mais educada) resposta para isso é que certamente, em algum momento do seu dia como desenvolvedor de aplicações Java EE, você precisa lidar com diversos class loaders, de uma maneira ou de outra. Todas as vezes que você faz uma página JSP ou escreve uma “linda” action para o Struts e faz o deploy dos mesmos no seu servidor de aplicações, este código é carregado por um class loader especial que previne que outras webapps tenham acesso a estes componentes, mesmo que elas residam dentro de uma mesma JVM. A pior (e mais mal-educada) resposta para isso é que meu email não serve para suporte técnico (pague por isso), mas para que pessoas me convidem para tomar um choppinho em algum boteco de vez em quando. Além disso, aprender nunca é demais e você já deveria estar sabendo disso (que aprender nunca é demais)
Antes de tudo, vamos estabelecer a diferença entre tipo e classe. Tipo (ou também Class Type se você preferir) é o nome completo de uma classe, i.e., o nome da classe junto com o nome do pacote que o contém. Por exemplo, com.acme.Foo representa um tipo. Contudo, com.acme.Foo não é exatamente uma classe do ponto de vista que queremos analisar, pois uma classe no nosso contexto é formado por um class type junto com o class loader que foi responsável por carregar e definir este tipo. Em outras palavras, uma classe é uma instância válida do tipo java.lang.Class, e esta faz referência a um class type e ao class loader que o carregou efetivamente. Muito bem, estabelecido este conceito básico, vamos às regras do jogo.
A primeira regra que você precisa saber sobre carregamento de classes em Java é que os class loaders são organizados de maneira hierárquica, conforme a figura abaixo:

Todas as vezes que você inicia uma JVM, um class loader é instanciado e responsável por carregar todas as classes do JRE e extensões. Este é o famoso o Bootstrap Class Loader e é a raiz da sua hierarquia de class loaders. Em seguida, um segundo class loader, o System Class Loader, é responsável por carregar todas as classes listadas na variável de ambiente CLASSPATH ou passadas como argumentos na inicialização da JVM através do parâmetro –classpath. por sua vez. A partir deste ponto, você pode organizar outros class loaders da maneira que você quiser, mas sempre tendo em mente que seus class loaders residem dentro da hierarquia desenhada acima, onde alguém é sempre pai de alguém (implicitamente, todo mundo é filho de SystemCL). Além disso, é preciso lembrar-se também de outra regra muito importante e que rege o comportamento geral da maioria dos class loaders, que é como funciona o processo de carregamento de classes em si. E por carregar uma classe entende-se o processo de ler, byte por byte, o bytecode de uma classe, criar uma instância de java.lang.Class que defina aquela classe e mantê-la em cache dentro do class loader.
Você sabia que o class loader que inicia o carregamento de uma classe não é necessariamente aquele que carrega/define a classe de fato? Isso ocorre pois a regra geral (que você pode ler no Javadoc da java.lang.ClassLoader) de relacionamento entre class loaders é a seguinte: a menos que você precise muito, você deve delegar o carregamento de uma classe para um class loader que resida em um nível hierárquico acima do seu class loader. Isso significa que, quando o seu class loader quiser carregar uma classe, ele deve delegar este processo para seu class loader pai primeiro e apenas se este não conseguir encontrar a classe desejada, então assumir a responsabilidade de tentar carregar o recurso desejado. Esta é a segunda regra do jogo mas pode haver exceções. A exceção é exatamente quando você precisa muito que seu class loader seja o responsável por carregar e definir uma classe qualquer. Isso acontece sempre que você precisa reduzir o escopo de uma determinada classe dentro da sua JVM e o melhor exemplo disso são os servlet containers como o Tomcat, que nunca delegam a definição de uma classe que pertença a uma webapp para o seu class loader pai.
Esta exceção implementada nos servlet containers (e nos EJB containers também) é conseqüência da terceira regra do jogo: um class loader pode ter apenas uma única representação de um tipo carregada. Isso significa, por exemplo, que você só pode ter um único class type com.acme.Foo dentro de um mesmo class loader. Logo, se sua JVM não possuir múltiplos custom class loaders, sinto muito, apenas uma representação de com.acme.Foo poderá residir na sua JVM, já que esta já deve ter sido carregada pelo SystemCL. Vale lembrar também que, dada a segunda regra do jogo, a menos que você precise muito, você deve delegar o carregamento e a definição de uma classe para um class loader pai, o que, se levado a sério por todo mundo, nos leva a ter apenas uma única representação do class type com.acme.Foo na nossa JVM, o que, por conseqüência, tornaria os servlet containers impossíveis de existirem. Por isso que você só vai violar a regra 2 quando você precisar muito.
E sabendo destas 3 regras do jogo e sabendo como os EJB containers funcionam e porquê funcionam do jeito que funcionam, meu colega descobriu que ele não poderia acessar um EJB de uma outra aplicação usando uma Local interface e que ele precisaria descobrir como obter uma Home interface (J2EE 1.3 não tem LocalHome interfaces
) para fazer o trabalho sujo dele. E antes que você me diga que eu simplesmente deveria ter simplesmente dito a ele que “para obter referência a EJBs que não residem em sua aplicação ele deveria usar uma Home Interface” e encurtado a história, eu respondo: o que resolveria dar a solução sem ele entendê-la?
Update: O Rodrigo postou uma ótima continuação sobre class loaders em seu blog. (E os malditos trackbacks do meu blog pararam de funcionar
)
Popularity: 92% [?]