Depois de um bom tempo sem postar, vou falar um pouquinho sobre anotações em Java. Quem já programa em Java, já deve ter visto algo como @Override, @Deprecated, @SupressWarnings, ou algo do tipo. Estas são anotações adicionadas ao código-fonte que podem ter funcionalidade meramente indicativa ou auxiliar o compilador Java. Mas um recurso bacana das anotações é a capacidade de utilizá-las em tempo de execução para ajudar na lógica do sistema, permitindo inclusive criar bibliotecas reutilizáveis.
Um framework muito popular que é construído baseado em anotações é o Hibernate (que implementa a especificação JPA, Java Persistence API). Ele permite a persistência de qualquer tipo de objeto, desde que as classes desses objetos estejam com as devidas anotações.
Para ilustrar o uso das anotações, vou criar um mini-mini-mini Hibertnate :) Nele definiremos duas anotações: uma para determinar qual tabela a classe vai persistir seus objetos; e a segunda mapeará um atributo da classe a um campo da tabela.
Vamos começar pelas anotações:
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DBTable {
public String table();
}
A diferença básica de uma interface comum e uma annotation é o símbolo de @ (arroba) antes da palavra interface. Os "atributos" da anotação são métodos, que no exemplo acima é o nome da tabela no banco. As regras para os atributos/métodos é que eles só podem retornar: primitivos, Strings, enum, Class ou um array dos anteriores.
A anotação @Target indica para qual tipo de elemento java a anotação que estamos definindo irá tratar. Ele pode ser:
TYPE: para classes, interfaces, ou enums;
FIELD: para atributos;
METHOD: para métodos;
PARAMETER: para parâmetros de métodos;
CONSTRUCTOR: em construtores;
LOCAL_VARIABLE: em variáveis locais;
ANNOTATION_TYPE: em uma outra anotação;
PACKAGE: nos pacotes java.
Já a anotação @Retention indica por quanto tempo a anotação será retida. Pode assumir os valores:
RUNTIME: as anotações são armazenadas na classe e estão disponíveis em tempo de execução;
SOURCE: as anotações são armazenadas na classe, mas NÃO estão disponíveis em tempo de execução;
CLASS: as anotações NÃO são armazenadas na classe (default).
Por último, a anotação @Inherited indica que as subclasses herdarão essa anotação.
A anotação acima servirá para mapear uma classe em uma tabela do banco de dados.
Ela será utilizada apenas em classes, ficará disponível em tempo de execução e poderá ser herdada por subclasses que a usarem.
Vamos agora para a anotação que mapeará atributos em campos da tabela:
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBField {
String colummn() default "";
}
A anotação acima, é apenas para atributos, terá sua informação disponível em tempo de execução e terá o atributo column. Definimos um valor default para a coluna, para que o nome do atributo seja igual ao nome do campo da tabela, não seja preciso preencher.
Vamos agora usar essas anotações em duas classes: Pessoa e Funcionário. Essa segunda herdando da primeira:
@DBTable(table="TBPessoa")
public class Pessoa {
@DBField(colummn="str_nome")
private String nome;
@DBField(colummn="str_end")
private String endereco;
@DBField
private int idade;
public Pessoa(String nome,
String endereco, int idade) {
this.nome = nome;
this.endereco = endereco;
this.idade = idade;
}
public String getNome() {
return nome;
}
public String getEndereco() {
return endereco;
}
public int getIdade() {
return idade;
}
}
O mais importante dessa classe é o mapeamento que fizemos da classe com o nome da tabela e dos atributos com os campos. Tudo isso só usando nossas próprias anotações.
Agora a classe Funcionario:
@DBTable(table="TBFunc")
public class Funcionario extends Pessoa {
@DBField(colummn="num_ctps")
String ctps;
public Funcionario(String nome, String endereco,
int idade, String ctps) {
super(nome, endereco, idade);
this.ctps = ctps;
}
public String getCtps() {
return ctps;
}
}
Agora vamos criar a classe que lerá objetos (de qualquer classe) e procurará nossas anotações para fazer uma inclusão (FAKE claro :) no banco de dados.
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Set;
public class Repositorio {
public void insert(Object obj) throws Throwable {
// Tenta obter nossa anotação na classe
DBTable persistable =
obj.getClass().getAnnotation(DBTable.class);
// Se tiver a anotação...
if (persistable != null){
String tabela = persistable.table();
// Map para montar o SQL campo/valor :)
HashMap<String, String> keyAndValues =
new HashMap<String, String>();
// Obtém os atributos da classe via reflection
Field[] fields = getFields(obj.getClass());
for (Field field : fields) {
// como os atributos são private,
// setamos ele como visible
field.setAccessible(true);
DBField coluna =
field.getAnnotation(DBField.class);
// Se o atributo tem a anotação
if (coluna != null){
// Verifica se está vazio pra usar o
// nome do próprio atributo
String columnName =
coluna.colummn().equals("") ?
field.getName() : coluna.colummn();
// Adiciona campo/valor no map
keyAndValues.put(
columnName, field.get(obj).toString());
}
}
// Varre o map para montar o SQL
String values = "";
Set<String> keys = keyAndValues.keySet();
for (String campo: keys) {
if (!values.equals("")) values += ",";
String valor = keyAndValues.get(campo);
values += campo +"='"+ valor +"'";
}
String sql = "INSERT INTO "+ tabela +
" values("+ values +")";
System.out.println("SQL---->"+ sql);
}
}
// Método recursivo para obter os atributos da
// class e da superclasse
public Field[] getFields(Class c){
// Se tem superclasse
if (c.getSuperclass() != null){
// Chama o próprio método para pegar os
// atributos da superclasse
Field[] superClassFields =
getFields(c.getSuperclass());
// Pega os atributos da própria classe
Field[] thisFields = c.getDeclaredFields();
// array com todos os atributos
Field[] allFields = new Field[
superClassFields.length +
thisFields.length];
// Copia os atributos da superclasse
System.arraycopy(superClassFields, 0,
allFields, 0, superClassFields.length);
// Copia os atributos da classe atual
System.arraycopy(thisFields, 0, allFields,
superClassFields.length, thisFields.length);
return allFields;
// Se não tem superclasse, retorna os
// próprios atributos
} else {
return c.getDeclaredFields();
}
}
}
O código acima está todo comentado. Qualquer dúvida, deixem seus comentários... Ah mas ainda tem a classe que usa isso tudo :)
public class Main {
public static void main(String[] args) {
Repositorio repo = new Repositorio();
Pessoa p = new Pessoa("Nelson", "Rua tal", 28);
Funcionario f = new Funcionario(
"Glauber", "Rua x", 18, "1234");
try {
repo.insert(p);
repo.insert(f);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
O resultado é apresentado abaixo:
SQL---->INSERT INTO TBPessoa values(str_nome='Nelson',idade='28',str_end='Rua tal')
SQL---->INSERT INTO TBFunc values(str_nome='Glauber',idade='18',str_end='Rua x',num_ctps='1234')
Como podemos observar, o mesmo repositório está persistindo duas classes distintas, baseando-se apenas nas notações que usamos nelas. Esse recurso pode trazer inúmeras vantagens para o desenvolvimento de aplicações facilitando a componentização da sua aplicação.
4br4ç05,
nglauber
PS.: Me baseei nesse post.
Nenhum comentário:
Postar um comentário