Esta API geralmente é utilizada quando precisamos acesso a informações de forma dinâmica e é considerada um recurso avançado da linguagem, assim como deve apenas ser utilizada por programadores que conhecem profundamente os fundamentos da linguagem.
Embora seja um recurso poderoso, deve ser utilizado com cuidado, pois algumas características do Java (digamos assim) são sumariamente ignoradas, como por exemplo a segurança. Estou me referindo a acesso a métodos e propriedades private.
Bom, o caso é que nesta semana esta API foi a solução para algo que de outra forma seria impossível (ou muito trabalhoso
).
A tarefa a ser realizada era a seguinte: “Gerar um log de cada alteração realizada no banco de dados”. O caso é que o sistema não possuía nenhum tipo de log em nenhum lugar e nós temos mais 40 entidades de persistência (JPA). Então, como realizar tal log com o mínimo de trabalho possível? Como fazer isso sem ter que editar cada uma das entidades ou trechos de código dos demais programas envolvidos, verificando se cada campo existente teve seu valor alterado?
A resposta já imaginada foi: “Vamos utilizar a API de Reflexão do Java” . Por que? Simples, com pouco código podemos verificar de maneira dinâmica, cada objeto e cada propriedade deste objeto, além de compará-lo a outro =)
Grosseiramente, como funciona o uso de reflexão?
Você verifica as propriedades, métodos, anotações (e coisas do tipo) de uma classe. Essa verificação lhe proporciona saber, por exemplo, a assinatura completa de um método. Outra coisa que se pode fazer, é utilizar esta definição para invocar métodos da classe que se está avaliando, utilizando um objeto dela.
Se precisa de um objeto, por que não utilizar simplesmente objeto.<metodo>?
Por que precisamos fazer isto de forma dinâmica, ou seja, sem explicitar qual método queremos chamar ou qual objeto estamos utilizando.
Mostre-me o código!!!
Bom, trouxe para vocês algum código de exemplo (a base de reflexão
). Isso já servirá para dar uma base para aqueles que procuram esta informação =)
Nota: Como eu disse, utilizamos em conjunto com o JPA, então você verá algumas aplicações específicas.
Primeiro o básico: Obtendo uma classe e um método desta classe (para ser posteriormente utilizado):
/**
* Obtém a classe de um objeto
*
* @param nome
* @return
*/
public static Class obterClasse(String nome){
Class classe = null;
try {
classe = Class.forName(nome);
} catch (ClassNotFoundException cnf) {
cnf.printStackTrace();
}
return classe;
}
/**
* Obtém um dado método da classe especificada
*
* @param classe
* @param nome
* @return
*/
public static Method obterMetodo(Class classe, Class[] params, String nome){
Method metodo = null;
try {
if (params != null)
metodo = classe.getMethod(nome, params);
else
metodo = classe.getMethod(nome);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return metodo;
}
Ok, e como invocar este método que encontramos para obter um valor de retorno?
/**
* Invoca um dado método do objeto especificado
*
* @param objeto
* @param metodo
* @param argumentos
* @return
*/
public static Object invocaMetodo(Object objeto, Method metodo, Object[] argumentos){
Object obj = null;
try{
if (argumentos == null)
obj = metodo.invoke(objeto);
else
obj = metodo.invoke(objeto,argumentos);
} catch (IllegalAccessException ia){
ia.printStackTrace();
} catch (InvocationTargetException it){
it.printStackTrace();
}
return obj;
}
Ok, e o exemplo prático???
Bom, vou demonstrar um modo de se obter um HashMap onde as chaves são os campos de definem a chave primária de uma entidade de persistência qualquer, e os valores são os valores destes campos. Isso serve para se realizar uma consulta ao banco de dados sem saber explicitamente, qual a entidade com a qual estamos trabalhando.
/**
* Obtém a chave primária do objeto de persistência
*
* @param classe
* @param obj
* @return
*/
public static HashMap obterChavePrimaria(Class classe, Object obj){
HashMap chaves = new HashMap(){
@Override
public String toString(){
String toString = "";
for (Object propriedade : this.keySet())
toString += propriedade + "=" + this.get(propriedade) + "|";
return toString;
}
};
AnnotatedElement[] annotatedElements = classe.getDeclaredFields();
for (AnnotatedElement ane : annotatedElements)
if ((ane.getAnnotation(obterClasse("javax.persistence.Id")) != null) ||
(ane.getAnnotation(obterClasse("javax.persistence.EmbeddedId")) != null))
chaves.put(((Field)ane).getName(), obterValorDoCampo(classe,obj,((Field)ane).getName()));
return chaves;
}
/**
* Obtém o valor de uma propriedade do objeto. Esta propriedade deve possuir um
* método get<NomeDoCampo>.
*
* @param classe
* @param objeto
* @param nomeDoCampo
* @return
*/
public static Object obterValorDoCampo(Class classe, Object objeto, String nomeDoCampo){
nomeDoCampo = "get" + ("" + nomeDoCampo.toCharArray()[0]).toUpperCase() + nomeDoCampo.substring(1);
return invocaMetodo(objeto,obterMetodo(classe,null,nomeDoCampo),null);
}
Isto nos permite a fazer coisas do tipo:
@PreUpdate
public void preUpdate(Object objetoReferencia){
Class classe = obterClasse(objetoReferencia.getClass().getName());
chaves = obterChavePrimaria(classe,objetoReferencia);
.....
}
O método acima roda quando ocorre algum evento de update do banco para a persistência. Quando isto ocorrer, uma instância da entidade de persistência será enviada por parâmetro ao método, e sem saber que entidade é essa, podemos descobrir sua chave primária (com valores atribuídos) e fazer alguma coisa com isso, como uma consulta por Primary Key =)
Agora, deixo por conta da criatividade de vocês!
Muito bom o artigo parabéns!
Realmente muito interessante seu post mas fiquei com uma dúvida.
Como eu vou fazer para que a minha biblioteca de persistecia, por exemplo hibernate chame meu método de log no momento em que o método update dfa persistencia for executado.
[]’s Anselmo Battisti
Obrigado
Existem algumas anotações que podem ser adicionadas à métodos que devem ser executados quando determinado evento ocorre. Por exemplo, no final do artigo é utilizada a anotação @PreUpdate. Esta é uma das anotações do JPA conhecidas como “Callback Annotations”.
Dê uma olhada em como funciona esta parte do JPA, é bem interessante.
Ótimo exemplo prático de reflexão em Java!
Obrigado e parabéns!