| |
A
construção de componentes utilizando
a API Reflection pode levar a um reaproveitamento
maior de código, porém a existência
de algumas exceções pode tornar sua
utilização inviável. Por outro
lado, as Annotations permitem a inserção
de meta-informações em atributos,
métodos e classes, permitindo um tratamento
diferenciado, de forma que dentro da rotina que
utiliza Reflection as exceções possam
ser tratadas da forma correta. Neste artigo, será
mostrado como combinar essas duas tecnologias para
criar componentes mais flexíveis e aumentar
a produtividade da equipe.
A API Reflection permite que seja possível
obter, em tempo de execução, informações
sobre classes, métodos e atributos. Com essa
informação, é possível
criar uma instância de uma determinada classe
que não se conhece em tempo de compilação,
ou mesmo executar um método dessa classe
possuindo somente seu nome e os tipos de seus parâmetros.
Essas funcionalidades são utilizadas em diversos
frameworks conhecidos para eliminar trabalhos considerados
braçais. Essa API é utilizada, por
exemplo, pelo WebWork e o Struts para colocar os
parâmetros recebidos pelo request dentro das
propriedades de um Java bean e pelo Hibernate para
popular os objetos com informações
de uma base de dados. Um grande amigo meu dizia
que conhecer e utilizar a API Reflection é
o que separa “os homens dos meninos”.
Os frameworks citados como exemplo no parágrafo
anterior se baseiam em arquivos XML auxiliares para
saberem quais classes devem ser instanciadas e quais
métodos devem ser chamados. Esses arquivos
XML possuem meta-informações a respeito
das classes, dizendo, por exemplo, se uma classe
é persistente ou qual classe deve atender
a uma determinada requisição. Até
hoje ainda não encontrei alguém que
fosse fã de um descritor XML, no máximo
costuma-se considerá-lo um mal necessário.
Para facilitar a vida do desenvolvedor no gerenciamento
dessas meta-informações, foram criadas
então as Annotations.
O objetivo deste artigo não é se aprofundar
nos fundamentos de Reflection e Annotations, mas
mostrar como se pode tirar proveito desses recursos
para resolver problemas do dia-a-dia. Inicialmente,
será dada uma rápida introdução
sobre essas duas tecnologias, porém tudo
será mostrado de uma forma rápida
e resumida, focando na base necessária para
o entendimento dos exemplos. O restante do artigo
mostrará situações em que o
uso das Annotations juntamente com a API Reflection
resulta em soluções flexíveis
e reutilizáveis. Cada exemplo será
baseado em problemas práticos (que pessoalmente
já precisei enfrentar), e durante a explicação
de cada um serão abstraídas situações
mais genéricas em que soluções
similares poderiam ser utilizadas. O código-fonte
completo dos exemplos está disponível
para download no site da revista.
A
API Reflection
Como
já foi dito, não é o objetivo
deste artigo descrever por completo a API Reflection,
porém esta sessão fará uma
pequena introdução e mostrará
os principais conceitos utilizados nos exemplos.
Para quem quiser uma introdução mais
detalhada sobre Reflection, eu indico o artigo “O
pacote java.lang.reflect” publicado na MundoJava
nº 8. A API Reflection nos permite obter, em
tempo de execução, meta-informações
a respeito de classes, como, por exemplo, quais
interfaces implementa, quais são seus métodos
e quais são seus atributos. Utilizando essas
informações, é possível
desenvolver um componente que, por exemplo, executa
um método sem o conhecer em tempo de compilação.
Muitas pessoas acreditam que utilizar as funcionalidades
da API Reflection é somente para aqueles
gurus que começaram a programar com 6 anos
e são iniciados em alguma irmandade secreta.
Na verdade, não existe muito segredo na utilização
de Reflection, inclusive as classes dessa API representam
entidades de um domínio conhecido por qualquer
programador: classes, métodos, atributos,
etc. Na tabela 1, estão apresentados exemplos
de código que mostram como executar algumas
funcionalidades-chave da API Reflection. O objetivo
dessa tabela é cobrir o necessário
para a compreensão dos exemplos deste artigo
e servir como uma referência rápida
para os desenvolvedores.

Tabela 1. Tabela
com algumas funcionalidades da API Reflection.
Os métodos
mostrados na tabela 1 para a recuperação
de métodos e atributos funcionam apenas para
membros públicos da classe, o que inclui
o que foi herdado das superclasses. Para recuperar
membros com outras visibilidades, como private,
protected e default, deve-se utilizar os métodos
que possuem “declared”, como getDeclaredMethods()
ou getDeclaredField(). Esses métodos retornam
membros declarados na classe, independentemente
de sua visibilidade. Dessa forma, um método
público da superclasse não pode ser
recuperado pelo método getDeclaredMethod(),
a não ser que esse método seja sobreposto
na subclasse. Com isso, caso seja necessário
recuperar todos os atributos de uma classe, não
importando sua visibilidade e onde foram declarados,
deve-se fazer um loop percorrendo todas as superclasses
(que pode ser recuperada com o método getSuperclass())
e chamando o método getDeclaredFields() em
cada uma delas. A seguir segue um pequeno trecho
de código que imprime todos os atributos
de uma classe incluindo os herdados das superclasses,
independentemente do nível de acesso.
Class
classeBase = Class.forName(“org.mundojava.Classe”);
for(Class classe = classeBase; classe != null; classe
= classe.getSuperclass())
for(Field f : classe.getDeclaredFields())
System.out.println(f.getName());
Algumas
novidades do J2SE 5.0, como o autoboxing/unboxing
e o varargs, facilitaram bastante a utilização
da API Reflection. Para a invocação
de um método, é necessário
passar como parâmetro um array de objetos,
contendo os parâmetros que serão passados
para o método. Quando um dos parâmetros
era de um tipo primitivo (exemplo, int), era preciso
utilizar a classe wrapper para passar o parâmetro
(exemplo, Integer). Com os recursos de autoboxing
e unboxing o tipo primitivo pode ser passado diretamente
como parâmetro sem a necessidade do uso do
wrapper. O método invoke() da classe Method,
para simplificar, agora suporta o uso de varargs,
o que permite passar os parâmetros diretamente
sem a necessidade da criação do array.
Segue um exemplo ilustrando como a utilização
da API foi simplificada com utilização
desses novos recursos. Pode paracer estranho na
primeira linha serem passados dois parâmetros
e na segunda linha serem passados três, porém
é isso mesmo, pois o recurso de varargs faz
com que cada item do array possa ser incorporado
como um parâmetro no método invoke().
J2SE
1.4: metodo.invoke(objeto,
new Object [] {“Parametro”,new Integer(10)});
J2SE 5.0: metodo.invoke(objeto, “Parametro”,
10);
A utilização
de Reflection em uma aplicação tem
duas principais desvantagens: a perda em desempenho
e a maior possibilidade de erros. Invocar métodos
e recuperar atributos utilizando Reflection é
muito mais custoso em termos de desempenho do que
se o mesmo fosse feito da forma normal. Como foi
dito no artigo “Otimização de
Performance para Aplicações Distribuídas”
na MundoJava nº 17, quando estamos otimizando
uma funcionalidade, o gargalo deve ser detectado
e o esforço deve ser priorizado em cima dele.
Felizmente, na maioria dos casos o gargalo não
se encontra na utilização de Reflection,
porém vale a pena tomar cuidado para não
abusar e não usar dentro de loops muito grandes,
etc. Normalmente, os ganhos em reusabilidade e flexibilidade
compensam bastante as pequenas perdas em desempenho.
Se o Hibernate e o Struts utilizam Reflection e
são utilizados em aplicações
eficientes e robustas, por que o seu componente
não pode usar?
Vários erros que são pegos em tempo
de compilação só aparecem em
tempo de execução com a utilização
de Reflection. Como exemplo pode ser citada a recuperação
de um método que não existe ou a invocação
de um método com parâmetros de tipos
errados. Isso é um problema, pois a aplicação
fica suscetível a um maior número
de erros, o que, se não for devidamente tratado,
pode causar impactos na robustez. Felizmente, com
um bom mecanismo de tratamento de erro e a utilização
de testes unitários, é possível
criar componentes robustos e confiáveis com
a utilização de Reflection.
Apesar da API Reflection possibilitar a criação
de componentes mais flexíveis, ela não
pode ser considerada a cura para todos os males.
Em muitos casos, as técnicas de orientação
a objetos e os padrões de projeto são
a melhor opção para dar a flexibilidade
adequada a uma aplicação. A criação
de instâncias utilizando Reflection, por exemplo,
deve ser feita quando o componente só tem
o conhecimento da classe que deve ser criada em
tempo de execução. Em outros casos,
padrões de projeto como Abstract Factory
ou Factory Method podem ser utilizados de forma
mais adequada. O polimorfismo é uma característica
das linguagens orientadas a objetos que permite
que, a partir de uma interface ou uma superclasse
em comum, objetos diferentes possam ser tratados
da mesma forma, mesmo que possuam comportamentos
diferentes. A API Reflection não deve ser
utilizada para esses casos e sim em rotinas em que
não faça sentido os objetos compartilhem
a mesma interface, como em Java beans, por exemplo.
Deve-se tomar muito cuidado, pois o uso inadequado
de Reflection pode adicionar complexidade desnecessária
e causar perdas de performance, sem se ter nenhum
ganho real com a sua utilização.
Uma funcionalidade avançada da API Reflection,
adicionada a partir da J2SE 1.4, são os proxys
dinâmicos. Proxy é um padrão
de projeto em que uma classe implementa a mesma
interface de uma outra e assume seu lugar, delegando
a chamada de métodos para a classe original
quando necessário. Um proxy dinâmico
consegue interceptar os métodos sem precisar
implementar a mesma interface da classe original,
assumindo dinamicamente esse comportamento. Dessa
forma, a mesma implementação de um
proxy dinâmico pode ser utilizada em diversas
interfaces. Os proxys dinâmicos serão
utilizados no último exemplo deste artigo
e serão explicados com mais detalhes durante
o mesmo.
Leia
o artigo completo na revista MUNDOJAVA, já
nas bancas!
|
|