Reflection e Annotation

Tempo de leitura: menos de 1 minuto

Falarei hoje de coisa antiga, mais muito útil. Me refiro à Reflexões (Reflections) e Anotações (Annotations).

Nivelando conceitos

Reflexo de um pessoa em um espelho d'águaReflexão (javax.reflection) é um recurso do Java, que permite criar chamadas em tempo de execução, sem precisar conhecer as classes e objetos envolvidos (tempo de compilação). Parece estranho dizer isso, mais existem situações, que nosso programa só descobre serem necessárias ao receber dados, em tempo de execução.

A reflexão inicia na Class. Esta, representa a o modelo (uma forma), é a partir dela que obtemos as informações dos atributos, métodos, construtores, etc…

Anotações (java.lang.annotation), foi liberado no Java 5, com o objetivo de adicionar metadados em nossos objetos afim de configura-los. Antes das anotações, era usual fazer configurações em arquivos XML a parte. As anotações vieram para facilitar isso.

A sintaxe de uma anotação é facilmente notada, sempre que você encontrar o carácter “@” seguido de uma palavra você está diante de uma anotação. Exemplos:

[code language=”java”]
@Override
@Test
[/code]

Importante saber que a presença de uma anotação não influi no comportamento da classe, atributo ou método daquele objeto, são apenas metadados, marcações, são códigos não executáveis.

A primeira vez é inesquecível

Quando comecei trabalhar na Synchro, sabia que existia, que era muito legal e útil, mas nunca havia usado.

Algum tempo depois, conhecendo melhor a aplicação vi em alguns pontos do sistema o uso de reflexão.

A primeira vez que realmente usei este recurso (reflexão), foi na construção de um utilitário. Existia na aplicação um trecho de código que era muito usado e replicado, era necessário tratar as máscaras de alguns atributos na entidade. Sempre antes de persistir o objeto, a classe de negócio da entidade (Serviço), tratava ou atribuía uma máscara para cada um dos atributos necessários, isso estava ficando trabalhoso, pois sempre que era necessário fazer alguma alteração, tínhamos que sair caçando onde precisávamos modificar.

A solução dada foi, a criação de uma Annotation, assim ficaria fácil anotar os atributos que precisavam ser tratados.

[code language=”java”]
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Mascara {
/**
* define a máscara a ser usada
* @return mascara.
*/
public String value();
}
[/code]

Nota: O código de uma anotação é bem simples, na declaração usamos as anotação @Retention@Target, a primeira para indicar quando a anotação será usada, no caso, iremos usa-la em runtime e a segunda especifica onde a anotação pode ser usada na classe. Outro ponto importante é o @interface, essa palavra indica que a classe é uma anotação.

Os atributos que precisavam receber o tratamento de máscara, começaram a ser anotados com @Mascara. Abaixo um exemplo de uso da anotação em uma entidade:

[code language=”java”]
public class Pessoa {
@Mascara("##.###.###/####-##")
private String cnpj;

@Mascara("###.###.###-##")
private String cpf;

}
[/code]

Mas isso não é o suficiente para os atributos receberem o tratamento de máscara, como dito anteriormente, a anotação é apenas um metadados da classe, ele não é executável. É aí que entra a reflexão.

Para a anotação funcionar, foi preciso criar um utilitário que pudesse receber o objeto e “filtrar” a definição da classe, a fim de encontrar os atributos anotados e submete-los ao tratamento corretamente. Fizemos algo parecido com o código abaixo:

[code language=”java”]
public class MascaraUtil{

public void aplicarMascara(final Object objeto) {
// como dito, tudo inicia no class do objeto
final Class<?> classe = objeto.getClass();

// Isso já é reflexão
// Recupero todos os fields (atributos) da class
final Field[] fields = classe.getDeclaredFields();
for (final Field field : fields) {
// para cada um dos fields da classe, verifico se a anotação @Mascara está presente.
if (field.isAnnotationPresent(Mascara.class)) {
// se estiver presente, recupero a anotação do field para extrair a máscara que deve ser dado ao atributo
final Mascara annotation = field.getAnnotation(Mascara.class);
final String mascara = annotation.value();
try {
// para obter acesso a um atributo privado, primeiramente devo liberar o acesso.
field.setAccessible(true);
// recupero o valor do atributo por reflexão
final String valorDoAtributo = (String) field.get(objeto);
try {
// com a máscara e o valor do atributo em mão, aplico a máscara ao valor.
final String valorComMascara = getValorComMascara(mascara, valorDoAtributo);
// depois, seto valor tratado ao field por reflexão.
field.set(objeto, valorComMascara);
} catch (final AplicacaoMascaraException e) {
// caso algum erro aconteça, apenas logo o erro e continuo aplicar a máscara nos outros atributos
e.printStackTrace();
}
} catch (IllegalAccessException e) {
// caso algum erro aconteça, apenas logo o erro e continuo aplicar a máscara nos outros atributos
e.printStackTrace();
}
}
}
}

private String formatarValorMascara(final String pTexto, final String pMascara) throws AplicacaoMascaraException {
// para formatar o valor, utilizo de um utilitário do swing (javax.swing.text.MaskFormatter)
MaskFormatter mf;
try {
mf = new MaskFormatter(pMascara);
mf.setValueContainsLiteralCharacters(false);
return mf.valueToString(pTexto);
} catch (final ParseException e) {
throw new AplicacaoMascaraException("Máscara ‘" + pMascara + "’ não pode ser aplicada ao valor ‘" + pTexto + "’", e);
}
}

private String getValorComMascara(final String pMascara, final String pValor) throws AplicacaoMascaraException {
//primeiro passo da aplicação da máscara, é remover qualquer caracter especial que o valor possa ter.
final String valorSemMascara = removerMascara(pValor);
// depois checo, que a quantidade de caracteres do valor e da máscara são iguais
final Integer quantCaracterNaMascara = pMascara.replaceAll("[^A#]", "").length();
if (valorSemMascara.length() != quantCaracterNaMascara) {
// se não lanço exception dizendo que a máscara não pode ser aplicada ao valor
throw new AplicacaoMascaraException("Valor não é compativel com o tamanho da mascara");
}
// retorna valor formatado com a máscara
return formatarValorMascara(valorSemMascara, pMascara);
}

private String removerMascara(final String pValor) throws AplicacaoMascaraException {
if (pValor == null) {
throw new AplicacaoMascaraException("O conteúdo do campo está nulo.");
}
//remove caracteres especiais
return pValor.replaceAll("[^A-Za-z0-9]", "");
}
}
[/code]

Comentei o código para ficar mais claro o que vai acontecer quando o objeto for submetido ao tratamento. Mas para aumentar a fixação, vamos rever os principais pontos do código.

Na linha 5 obtemos a class, na reflexão, onde tudo inicia. Através da class, é possível obter todos os fields, methods, modifiers, contructors, annotations, etc… Na linha 9, recuperamos um array contendo todos os atributos declarados na classe, lembrando que anotamos com @Mascara, os atributos que necessitavam ser tratados.

Para cada um dos atributos, na linha 12, verificamos se ele foi anotado com @Mascara, estes são atributos que precisamos tratar. Se o atributo estiver anotado, obtemos a anotação na linha 14, e acessamos o método value(), para obter seu valor, linha 15.

Como os atributos normalmente são definidos com private, precisamos liberar o acesso ao conteúdo do field, fizemos isso na linha 18. Finalmente na linha 20, obtivemos o valor do atributo.

Nota: Filed.get() e Field.set(), podem lançar java.lang.IllegalAccessException, isso porque o acesso ao atributo pode estar restrito, por isso a necessidade do try/catch.

Na linha 23, submeto o valor do atributo e a máscara da anotação ao método que getValorComMascara(), responsável efetivamente por fazer o tratamento, e retornar o valor com a máscara aplicada.

Valor com máscara aplicado em mãos, basta agora setar o valor ao atributo, através do método set(), na linha 25.

Para finalizar, vamos executar o utilitário e ver o resultado:

[code language=”java”]
public static void main(String[] args) {
final Pessoa pessoa = new Pessoa();
pessoa.setCpf("01234567890");
System.out.println("Antes : " + pessoa.getCpf());

new MascaraUtil().aplicarMascara(pessoa);
System.out.println("Depois: " + pessoa.getCpf());
}

//Saída
//Antes : 01234567890
//Depois: 012.345.678-90
[/code]

Conclusão

Vimos neste post, um pouquinho de conceito sobre reflexão e anotação, pois esse assunto é bem extenso, é possível fazer muitas coisas bacanas com reflexão, o grande problema é o custo da utilização. Vimos também na prática, o uso de anotação e reflexão para resolver problema de tratamento de máscaras em atributos de uma entidade.

Espero que tenham gostado, caso tenham alguma dúvida ou sugestão estou a disposição.

Está disponível no Github o código fonte do exemplo.

Abraços.

Nenhum comentário


  1. Que doido este assunto de Reflexões e Annotation em Java, sempre tive a duvida de como funcionavam aquelas anotações nas API’s MVC, DI e testes, agora sei a mágica 😀

    Responder



Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *