Introspeção

Metadados

Java é,desde o principio, uma linguagem baseada em metadados. Todos os objetos manipulados são associados com os metadados da sua estrutura. Se todos os objetos são instâncias de classes nada mais obvio que associar a instância à classe usada para a criar. Em Java instâncias da classe Class representam os metadados das instâncias das classes que a JVM manipula.

Porque Class é ela própria uma classe com instâncias, essas instâncias estão também associadas as classes. Este modelo permite que todos os objetos, inclusive os de metadados, sejam associados a classes.

reflection1_

Ilustração 1: Modelo resumido de  metadados do Java

A figura mostra um modelo resumido das classes de metadados presentes no Java. Class é o centro do mecanismo. Os métodos desta classe têm acesso ao ClassLoader corrente e permitem instanciar objetos da classe via newInstance. O método estático forName permite carregar um arquivo .class (presente no classpath) através do nome completamente qualificado da classe ( pacote + nome da classe). Este método é util para carregar classes dinamicamente. Normalmente a classe carregada implementa alguma interface pre-definida (ou herda uma super classe especifica) portanto a aplicação não manipulará a classe diretamente mas apenas a carrega numa variável polimórfica. Esta técnica é utilizada em muitos frameworks como Servlet API, EJB e em todos em que classes possam ser configuradas em parâmetros. Este mecanismo é utilizado também para carregar os drivers JDBC (que, assim, se auto registram no DriverManager)

Uma classe java é composta por vários tipos de membros : Métodos, Atributos e Construtores. Class permite pesquisar todos estes membros.

Métodos

Métodos são representados por Method que permite conhecer a assinatura do método (os tipos dos parâmetros, e o tipo de retorno). Além disso permite também invocar o método. Métodos não são amarrados a objetos nem a classes. Isto é muito interessante porque permite invocar o método sobre qualquer objeto que tenha um método com a mesma assinatura.

Este modelo desacoplado permite invocar qualquer método desde que saibamos os parâmetros necessários e o objeto sobre o qual invocar o método. Uma das técnicas avançadas de introspeção, bastante utilizada, é interceptar a execução de métodos com outros métodos. Isso é feito pela construção de proxies dinâmicos Estes proxies criam adaptadores (ou até alteram o bytecode da classe) de forma a substituir um ou vários métodos por um método especial. Este método especial recolhe os dados dos parâmetros e objeto sobre o qual o método foi invocado e os passa a um outro método que irá realizar alguma execução especial. O método original é também preservado e passado para o método de invocação. Desta forma é possível executar código antes e/ou depois do método original. Este é a implementação titica do paradigma de Programação Orientada a Aspectos.

É ainda possível não executar o método original baseado em alguma condição ou executá-lo em loop. Outra opção é enviar os dados da invocação pela rede e fazer essa invocação em outra JVM. Isso é um processo básico de invocação remota em que você pode escolher o protocolo que quer usar.

Embora os métodos possam ser executados ad hoc java não utiliza o conceito de método (aka função) como cidadãos de primeira classe. Uma das propostas para incluir closures em Java é exactamente dar aos métodos o lugar de cidadãos de primeira classe (junto com sintaxe especial , etc..). Esta opção que parece a mais obvia (JavaScript usa algo semelhante) parece não ser suficiente para o conjunto de funcionaldiades que uma closure tem que ter, mas a sua simplicidade faz com que ainda seja uma candidata. No fim, qualquer implementação de Closures irá implicar em algum tipo de metadado para elas e o candidato obvio é Method. Teremos que esperar para ver onde esse caminho vai dar…

Atributos

Atributos são representados por Field que permite conhecer o tipo do atributo e operarar sobre a variável que o representa no objecto. Os métodos get e set – à semelhança de invoke – recebem como parametro o objeto sobre o qual a modificação será feita. Ao contrário de Method, Field é propriedade de um certo tipo de classe não podendo ser invocado ad hoc.

Construtores

Construtores são representados por Constructor que de forma semelhante a Method permite invocar o construtor passando os parâmetros. O método newInstance de Class irá invocar o contrutor sem parâmetros. Isso é bastante conveniente (e uma das razões para o padrão JavaBeans seja ter classes com construtores sem parâmetros), mas a pesquisa e uso direto do construtor permite-nos criar objectos de qualquer classe com qualquer numero de parâmetros.

Claro que uma invocação utilizando um construtor com parâmetros nos obriga a saber quais são esses parâmetros e que objetos utilizar em cada um. Esse é o trabalho feito pelos chamados motores de injeção que compõe o coração de frameworks de injeção de dependências. Esses frameworks não passam de formar de criar um modelo de dependência de forma que o contrutor correto seja invocado com os objetos certos. Esse motores normalmente ainda suportam a invocação de métodos adicionais (após o objeto ter sido criado pela invocação do construtor) e até a alteração direta de atributos. O segredo desses motores é fazer essa logica de forma automática e o poder dos frameworks deste tipo está em criar o modelo de dependencia da forma mais simples possível. Antigamente XML era utilizado. Hoje temos a opção de usar Annotações.

Anotações

O Java 1.5 trouxe o conceito de Anotação. Uma interface que pode ser associada com qualquer componente do codigo. O objetos que representam esses componentes contém funcionalidade para localizar e ler essas anotações. Desta forma podemos enriquecer o modelo de metadados que o Java já tem, com metadados específicos das aplicações ou gerais como a anotação @Override que o Java não requer ( ao contrário de .NET por exemplo) mas que uma vez lá pode ajudar o compilador e descobrir erros. Outra anotação util é @Deprecated que vem substituir a anotação Javadoc.

Anotações podem ser lidas para pacotes, tipos ( classes, interfaces, enum e annotações), atributos, métodos, , construtores e parametros de métodos e construtores. O Java 1.7 prevê extender o modelo de metadados permitindo acoplar anotações também em variávels internas dos métodos. O mecanismo de anotações é muito mais poderoso que simples introspeção em runtime. É possível introspeção em compile-time. Pre-processadores podem ler arquivos fonte para criar outros arquivos fonte de forma automatizada através dos uso de anotações.

Permissões

O modelo de metadados do Java é bastante completo e poderoso. Isso pode representar um risco de segurança. De que vale definir o seu atributo como private se é possível acessá-lo via introspeção ?

Para resolver isso todos os tipos de metadados contam com o método getModifiers que retorna um inteiro. Este inteiro é codificado com as informações sobre a accessibilidade do método, atributo, construtor, classe , etc… A classe utilitário Modifier permite descobrir todas as opções baseado nesse único inteiro. Se o membro é protegido, abstrato, estático, final, etc.. tudo isso pode ser investigado.

Com base nessas informações o acesso é impedido. Quando uma classe tenta invocar set() em um objeto Field uma exceção é lançada de o campo não é publico. Isso pode ser contornado invocando setAccessible(true) no campo. Isto realmente dribla a intenção declarada que autor da classe e dá acesso “indevido” ao atributo.

Em sistemas normais onde várias frameworks que utilizam introspeção são utilizados (Hibernate, Spring, EJB, etc… ) não ha vantagem em empedir estes acessos “indevidos”. Contudo, em um ambiente controlado eles precisariam de permissão explicita.

A permissão pode ser controlada ( como outras) por meio de um SecurityManager instalado na inicilização. Assim, quando tentar invocar setAccessible(true) uma exceção de segurança será lançada. Apenas invocações devidas serão autorizadas. Invocações “indevidas” podem ser autorizadas explicitamente através do mecanismo de políticas (policy) padrão.

Resumo

A construção de objetos a partir de classes é algo muito sério para um linguagem orientada a objetos. Java parte de um meta-modelo que permite trabalhar com qualquer classe de objetos. Além disso permite que as aplicações analisem e manipulem esses metadados. A maior parte das funcionalidades e facilidades de frameworks advém desta capacidade, que só aumentou com a introdução das anotações.

Licença

Creative Commons License Sérgio Taborda
Este trabalho é licenciado sob a
Licença Creative Commons Atribuição-Uso Não-Comercial-Não a obras derivadas 3.0 Genérica .

2 opiniões sobre “Introspeção”

Deixe um comentário