3º resumo do livro .

                                              35 á 44       

Esse problema é muito mais grave do que aparenta; o tipo de erro que pode surgir é de difícil interpretação.

O menor dos problemas seria ter o comportamento da versão antiga do Hibernate.

 Mais grave é quando há métodos novos no JAR mais recente; a interface Session do Hibernate antigo seria carregada, e quando sua aplicação APP-nova invocar um método que só existe na versão nova, NoSuchMethod Error será lançado! Isso causa uma enorme dor de cabeça, pois o desenvolvedor fica confuso ao ver que o código compilou perfeitamente, mas durante a execução a JVM indica que aquele método não existe.

NoSuchMethodError é um forte indicador de que o código foi compilado esperando uma versão diferente de uma biblioteca que a encontrada em tempo de execução, provavelmente por causa de os jars estarem espalhados e compartilhados.

O comportamento da aplicação pode ser mais errático; imagine que a aplicação APP-nova utiliza diretamente uma classe do Hibernate que só existe na versão mais nova, como a TypeResolver. Carregamento de classes e classloader hell Casa do Código Essa confusão é similar ao DLL Hell, frequentemente chamado de Classloader hell na plataforma Java.

Em algumas configurações de classloaders diferentes, que carregam classes de diretórios em comum, pode aparecer um ClassCastException curioso e de difícil discernimento.

Se uma referência a um objeto do tipo Produto for passada como argumento para um método que recebe um objeto também deste tipo, mas esta classe tiver sido carregada por um classloader diferente, a exceção será lançada.

O desenvolvedor ficará confuso ao ver uma ClassCastException em uma invocação de método em que nem mesmo há um casting. Isso acontece porque, como vimos, em tempo de execução a identidade de uma classe não é apenas seu fully qualified name, mas também o classloader que a carregou.

Assim, uma classe com mesmo nome, mas carregada por classloaders diferentes, é outra. Isto é chamado de runtime identity e muito importante para permitir que containers tenham mais de uma versão da mesma classe na memória (como o exemplo das Sessions de diferentes versões do Hibernate.

A arquitetura tradicional dificulta que uma aplicação sobrescreva algum componente do Container classloader, mas esse modelo invertido (padrão no Tomcat, por exemplo) permite esse cenário.

 A ordem de resolução das classes passa a ser: primeiro o Bootstrap, depois as classes da aplicação e depois as compartilhadas do Container.

Criando seu ClassLoader Podemos instanciar a URLClassLoader para carregar classes a partir de um conjunto dado de URLs.

 Isto indica que seu parent classloader será o bootstrap diretamente.

Elas serão carregadas em ambos os casos pelo bootstrap classloader (representado pelo null), pois não há como evitar que esse classloader seja consultado em razão da questão de segurança já discutida.

 Ao remover o null do construtor, o URLClassLoader terá como parent o classloader que carregou o código sendo executado (o Application classloader), fazendo com que o carregamento da classe seja delegado para este, antes de ser tentado pelo nosso URLClassLoader.

Carregamento de classes e classloader hell Casa do Código O parent classloader será sempre consultado antes de o classloader de hierarquia “mais baixa” tentar carregar uma determinada classe.

 Lembre-se de que este é o motivo pelo qual jogar os JARs na variável de ambiente CLASSPATH pode acabar escondendo versões diferentes da mesma classe que estejam ao alcance de classloaders mais “baixos” na hierarquia.

Caso elas fossem incluídas dentro da biblioteca padrão, qualquer outro projeto que necessitasse delas em uma versão diferente (tanto mais atual quanto mais antiga) teria sempre a versão do rt.jar Para contornar o problema, a Sun colocou as classes que seriam do pacote org.apache para dentro de com.sun.org.apache.

Sem dúvida uma maneira deselegante, mas que evitou o problema de versionamento.

Este problema tornou-se ainda mais frequente.

 Nesses dois casos, para contornar o problema, utilizamos o recurso de endorsed JARs.

 Através da linha de comando (-Djava.endorsed.dirs), você especifica diretórios que devem ter prioridade na procura de classes antes que o diretório ext do Java SE seja consultado.

É uma forma de o administrador do sistema dizer que confia em (endossa) determinados JARs. Alguns servidores de aplicação possuem um diretório especial onde você pode jogar os JARs a serem endossados.

É comum aparecerem problemas com OutOfMemoryError, acusando que este espaço se esgotou, dado um número muito grande de classes carregadas.

 Em particular, isso ocorre facilmente depois de alguns hot deploys em um container.

 Toda classe carregada tem uma referência para o seu classloader, assim como todo classloader referencia todas as classes carregadas por ele.

 Isso para que possa devolver sempre a mesma classe no caso de outra invocação subsequente para o mesmo full qualified name, agindo como uma factory que cacheia suas instanciações. Esse relacionamento bidirecional entre Class e ClassLoader faz com que os objetos Class só sejam coletados pelo garbage collector junto com o classloader inteiro e todas as outras classes associadas.

Logo, a única maneira de um objeto Class ser coletado é se todas as referências para todas as classes do seu classloader forem liberadas também.

 Ao realizar o hot deploy de uma aplicação, o próprio container libera as referências das classes antigas, possibilitando a coleta do classloader do contexto.

Isso deveria ser suficiente para que o contexto fosse inteiramente liberado da memória, mas, na prática, outro classloader acima daquele da WebApplication segura referências para classes da aplicação.

O carregamento de um único driver JDBC é capaz de segurar na memória o contexto inteiro de uma aplicação que não é mais necessária.

 Na prática, é quase impossível uma aplicação Java conseguir evitar esses leaks de classloaders, por isso em algum momento surgem os OutOfMemoryError frequentes nos hot deploys, sendo mais um motivo para evitá-los em produção.

Todo o paradigma da orientação a objetos, seus princípios e boas práticas procuram trazer esses benefícios para o design.

 Ao pensar no sistema como um todo, outras questões de mais alto nível surgem, em especial aquelas que tratam da forma como os objetos se relacionam e sua organização dentro e entre sistemas.

Ao relacionar dois objetos distintos, deve-se levar em conta as boas práticas que serão discutidas nesse capítulo, como a diminuição do acoplamento entre objetos.

 Programe voltado à interface, não à implementação ao trabalhar com coleções, escolher a implementação certa para cada caso é uma tarefa difícil.

Cada uma delas, como ArrayList, LinkedList ou HashSet, é melhor 3.1. Programe voltado à interface, não à implementação Casa do Código para resolver determinas categorias de problemas.

 Pode ser muito arriscado escrever todo o código da aplicação dependente de uma decisão antecipada.

 Apesar disso, grande parte dos desenvolvedores opta por sempre utilizar ArrayList desde o início sem critério algum.

O problema em realizar uma mudança como esta, de implementação, é que todo código que usava o retorno do método como ArrayList quebra, mesmo que só usássemos métodos que também existem definidos em HashSet.

Seria preciso alterar todos os lugares que dependem de alguma forma desse método.

Mas sempre há também informações semânticas implícitas na utilização desse método, e que não são expostos através da assinatura.

Um exemplo de acoplamento semântico está em depender da informação de que uma List permite dados duplicados, enquanto um Set garante unicidade dos elementos.

Como problemas no acoplamento sintático são encontrados em tempo de compilação, os semânticos somente o são em execução, daí um motivo da importância de testes que garantam o comportamento esperado.

 Desta forma, alterar a implementação torna-se sempre muito mais custoso, caracterizando o alto acoplamento que tanto se procura evitar.

 Para minimizar esse problema, é possível usar um tipo de retorno de método mais genérico, que contemple diversas implementações possíveis, fazendo com que os usuários do método não dependam em nada de uma implementação específica.

Usar interfaces Java é um grande benefício nestes casos, pois ajuda a garantir que nenhum código dependa de uma implementação específica, pois interfaces não carregam nenhum detalhe de implementação.

A escolha da interface ideal vai depender do que você quer permitir que o código invocador possa utilizar e realizar na referência retornada.

Programe voltado à interface, não à implementação Casa do Código de elementos que essa coleção possui, nem os elementos de maneira aleatória através de um índice.

 Devemos procurar um balanço entre o desacoplamento e a necessidade do nosso código.

 Esta é a ideia do Princípio de Segregação de Interfaces: clientes não devem ser forçados a depender de interfaces que não usam.

Uma implementação de lista, MeuProprioArrayList, feita pelo desenvolvedor é provavelmente mais instável que a interface List, já que as forças que a impedem de mudar são fracas (não há outras classes utilizando essa implementação).

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *