Annotations: O que é, pra que serve?
Annotations… recurso interessante.
Sempre tive a curiosidade de saber como funcionavam, então fui perguntar pro Google.
Pretendo demonstrar aqui o que eu aprendi nas minhas pesquisas.
Annotations são… advinhem… anotações! É um jeito que você tem de fazer alguma marcação em um atributo, método, classe, entre outros.
Elas sempre devem ser digitadas antes do objeto que você queira anotar (método, atributo…) e o nome das anotações são sempre precedidos de @ (arroba). Ex:
@MinhaAnotacao class MinhaClasse{ }
Até ai tudo bem, mas você deve estar se perguntando: “Pra que diabos eu vou usar isso? Pra que ficar enfeitando meu código?”. Calma. Tudo tem seu propósito.
Para poder trabalhar com as anotações, iremos utilizar a reflexão computacional, portanto sugiro que você leitor, leia meus posts antigos sobre Reflexão Computacional:
Java e seus Espelhos: Reflexão Computacional e
Reflexão Computacional: Exemplo Prático
Daqui pra frente vou supor que você entende e sabe utilizar a reflexão computacional…
Exemplo 1: Testando seus métodos
Então vamos lá: imagine que você é um bom programador com uma ótima noção de engenharia de software (o que realmente deve ser verdade neh…
). Você sabe que uma fase importante no desenvolvimento de um software é a fase de testes. Porém você não gosta dos frameworks existentes para teste unitário (ou qualquer outro tipo), então você decide criar o seu próprio!
Para não ficar muito chato de utilizar, você decide que durante o desenvolvimento, todo método que possuir a anotação “@Testar” será testado pelo seu framework.
OK, com essa idéia na cabeça vamos criar a nossa anotação @Testar:
Testar.java
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Testar { }
Reparem que a nossa anotação deve ser anotada com meta-anotações.
A anotação @Retention(RetentionPolicy.RUNTIME) diz que a VM deve manter essas anotações em tempo de execução para podermos ler elas através da reflexão. Outras opções seriam: SOURCE e CLASS.
E a anotação @Target(ElementType.METHOD) indica que a nossa anotação só é aplicada a métodos. Outras opções seriam: FIELD, TYPE (classe e interface), PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, e PACKAGE.
Reparem que para declarar a nossa própria anotação, deve-se utilizar “@interface” antes do nome da anotação.
Pronto! Só isso! É assim que as anotações são criadas. Uma vez que já temos ela, vamos criar a classe que irá ser testada.
ClasseParaTestar.java
public class ClasseParaTestar { @Testar public void metodo1() { throw new RuntimeException("Deu zebra!"); } @Testar public void metodo2() { } @Testar public void metodo3() { } public void metodo4() { } @Testar public void metodo5() { throw new RuntimeException("Ixi maria... Erro!"); } public void metodo6() { } @Testar public void metodo7() { } public void metodo8() { } }
Como o propósito é só demonstrar as anotações, os métodos não fazem nada.
Beleza, agora os métodos que eu quero testar já estão “anotados”. Vamos criar a classe principal do nosso framework de testes.
Testador.java
public class Testador { public static void testar(Object obj) { Class<?> classe = obj.getClass(); int passou = 0; int falhou = 0; System.out.println("-------------Inicio dos Testes--------------"); for (Method m : classe.getDeclaredMethods()) { if (m.isAnnotationPresent(Testar.class)) { try { System.out.print("Testando o método: "; + m.getName()); m.invoke(obj); passou++; System.out.println(" => passou!"); } catch (Throwable t) { falhou++; System.out.println(" => falhou! " + t.getCause()); } } } System.out.println("---------------Fim dos Testes---------------"); System.out.println("Passaram: " + passou + ", Falharam: " + falhou); } }
Simples simples (caso você entenda a reflexão)… Tudo o que a gente faz é verificar se o método possui a anotação @Testar, caso ele possua, o método é invocado. Se não ocorrer nenhuma exceção, então ele passou no teste, caso contrário ele falhou.
Para utilizar o nossa pequeno (micro) framework, basta chamar o método estático passando um objeto da nossa classe para testar:
Testador.testar(new ClasseParaTestar());
Para o nosso exemplo, você deve obter a seguinte saída:
-------------Inicio dos Testes-------------- Testando o método: metodo1 => falhou! java.lang.RuntimeException: Deu zebra! Testando o método: metodo2 => passou! Testando o método: metodo3 => passou! Testando o método: metodo5 => falhou! java.lang.RuntimeException: Ixi maria... Erro! Testando o método: metodo7 => passou! ---------------Fim dos Testes--------------- Passaram: 3, Falharam: 2
Obs: Eu tirei este exemplo da documentação oficial.
Exemplo 2: Validando seus atributos
Vamos criar uma classe genérica que faz a validação dos atributos de qualquer classe que você peça para ele validar. É claro que os atributos a serem validados devem possuir as anotações que nós vamos criar.
Vamos criar as nossas anotações então: @ValidarContemTexto e @ValidarPositivo.
ValidarPositivo.java
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ValidarPositivo { }
ValidarContemTexto.java
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ValidarContemTexto { int min() default 1; }
Opa! Apareceu uma coisa nova aqui. A anotação @ValidarContemTexto possui um atributo???
É isso mesmo! As anotações podem conter atributos para realizar alguma lógica durante o processamento delas.
No nosso caso, a anotação @ValidarContemTexto irá validar se o atributo que está decorado com a anotação possui pelo menos min caracteres (que por default é 1).
Reparem que a meta-anotação @Target é ElementType.FIELD, porque vamos utilizar essas anotações para decorar atributos.
A classe Usuario para o nosso teste será a seguinte:
Usuario.java
public class Usuario { @ValidarContemTexto(min = 10) private String login; @ValidarContemTexto(min = 20) private String senha; @ValidarPositivo private int nivel; //Construtores, getters e setters //... }
O login deverá possuir pelo menos 10 caracteres, a senha deverá possuir pelo menos 20 caracteres (para garantir alguma segurança… hehehe) e o nível deverá ser um inteiro positivo maior ou igual a zero.
Vamos ver a classe responsável pela validação:
Validador.java
public class Validador { public static boolean validar(Object obj) { Class<?> classe = obj.getClass(); boolean ok = true; for (Field f : classe.getDeclaredFields()) { f.setAccessible(true); //Annotation @ValidarPositivo if (f.isAnnotationPresent(ValidarPositivo.class)) { try { int num = Integer.parseInt(f.get(obj).toString()); if (num < 0) { ok = false; throw new Exception(); } } catch (Throwable t) { System.out.println(f.getName() + " deve ser um valor inteiro positivo."); } } //Annotation @ValidarContemTexto if (f.isAnnotationPresent(ValidarContemTexto.class)) { ValidarContemTexto anotacao = f.getAnnotation(ValidarContemTexto.class); try { String texto = f.get(obj).toString(); if (texto.length() < anotacao.min()) { ok = false; System.out.println(f.getName() + " deve possuir pelo menos " + anotacao.min() + " caracteres."); } } catch (Throwable t) { System.out.println(f.getName() + ": " + t.getCause()); } } } return ok; } }
Quase igual ao exemplo anterior, porém nós percorremos os atributos (getDeclaredFields()) ao invés do métodos (getDeclaredMethods()).
Um if para cada anotação… nada muito novo. A única novidade aqui é a recuperação da anotação @ValidarContemTexto para poder acessar o valor que foi definido para o seu atributo min. Mas também não é nada de outro mundo.
O código a seguir cria 3 usuários e executa a validação com cada um deles:
ArrayList<usuario> usuarios = new ArrayList<usuario>(); usuarios.add(new Usuario("login", "senha", 2)); usuarios.add(new Usuario("felipesaab", "SaabRocks", -5)); usuarios.add(new Usuario("javasimples", "javasimplesjavasimples", 0)); for (Usuario u : usuarios) { System.out.println("Validando usuario: " + u.getLogin()); if (Validador.validar(u)) { System.out.println("Usuário OK."); } else { System.out.println("Corrija os erros."); } System.out.println(); }
O resultado será o seguinte:
Validando usuario: login login deve possuir pelo menos 10 caracteres. senha deve possuir pelo menos 20 caracteres. Corrija os erros. Validando usuario: felipesaab senha deve possuir pelo menos 20 caracteres. nivel deve ser um valor inteiro positivo. Corrija os erros. Validando usuario: javasimples Usuário OK.
Chegamos ao fim de mais um post aqui do Java Simples. Espero que tenham gostado, eu pelo menos achei esse assunto interessantíssimo.
Quem quiser pode pegar os arquivos que eu fiz e testei aqui.
[]s e até a próxima,
Saab.
8 Responses so far
Maicon Lima
abril 8th, 2010
23:24
Nota 10 seu Post.
Preciso recuperar anotoções de Entity Classes que definem mapeamento Objeto/Relacional, para fazer um modelo de persistência mais leve e simples que Hibernate/JPA, etc.
Seu post caiu como uma luva.
Ótimo trabalho!
Saab
abril 9th, 2010
8:38
Vlw Maicon.
Boa sorte com o seu projeto. =]
Bruce Melo
abril 10th, 2010
21:48
Muito bom esse post, o de reflexão também, esclareceu todas minhas dúvidas, rsrs. parabéns saab você é o cara.
Frederico
dezembro 14th, 2010
14:44
Muito BOm mesmo!!!
Tava Precisando criar um metodo bem generico que acessasse algumas classes com metoodos em comuns.
Com esse post e os outros a que esse se referia (que são necessários para entender esse, hehehe) deu para enteder um pouco mais sobre reflexao.
Muito Bom!
Tudo de Bom flws!
Danilo
fevereiro 19th, 2011
10:17
Muito bom este post, como todos os outros do site! Parabens
Saab
fevereiro 19th, 2011
13:24
Vlw pessoal, que bom que gostaram e entenderam! =]
Max Mendes
março 1st, 2011
11:26
Cara, muito bom mesmo o post. Apesar de achar um pouco complicado para quem nunca tinha visto isso, mas deu pra aprender muito. Resta agora saber (talves porque eu não tenha absorvido toda informação deste post) como o JPA lê o mapeamento das classes. Seria pelo framework? abraço;
Saab
março 1st, 2011
20:41
Então Max, concerteza em algum lugar dentro do framework que implementa a JPA vai ter uma leitura das anotações das classes e atributos.
Se você estiver muito curioso baixe o código fonte do framework que vc está usando caso ele seja open source para ver como ele faz.
Boa sorte nos estudos.
Leave a comment