Java for Dinner

Um repositório sobre Java e tudo que gira ao redor dele.

  • Arquivos

  • Top Clicks

    • Nenhum

O poder das annotations em sistemas legados

Posted by Danilo Barsotti em 9 março, 2008

odos sabem como é complicado mexer com sistemas legados, ainda mais quando esse sistema é um dos principais de um grande banco brasileiro e com isso todo cuidado é pouco para não ultrapassar os prazos.

As Annotations:

Dentre várias novidades do java 1.5 uma delas foi as Annotations, elas nos dão a possibilidade de se trabalhar com meta-dados muito facilmente, com pouco esforço podemos fazer varias coisas que antes não poderiamos ou então era muito trabalhoso.

No projeto atual, dentre várias tarefas que tenho, uma delas era de incluir algumas validações ( + ou – 400 ) no sistema, pela quantidade de validações e pelo curto prazo para cumprir a tarefa, tive que analisar o impacto que isso poderia acarretar.

Conclusão, era inviavel colocar um monte de código novo para fazer essas validações, primeiro porque ia ser muito trabalhoso e segundo que era muito arriscado.

Então fiz algumas classes ( que alguns chamam de framework, rsrs ) que gerenciam anotações para fazer todo meu trabalho de validação de variaveis, entrada de parametros em métodos, retorno de métodos ( ai usei AOP ) e por ai vai.

Então, vamos ao que interessa!

Obs.: Como esse projeto já é usado pelo banco e por motivos de segurança, vou mostrar simples validações e não as que foram usadas no banco, mas o motor é o mesmo, somente a logica das validações foram retiradas.

Mão na massa

Quando comecei a programar essa framework, umas das coisas que eu tinha em mente era ela ser rápida e flexivel na programação das anotações.

Comecei programando o motor de gerenciamento das anotações, a classe esta muito bem comentada sendo assim, acho q dispensa maiores explicações:

/**
* Classe responsavel por gerenciar o funcionamento de todas as annotations
* usadas pela framework.
*
* Para cada anotação encontrada, é pego o valor do atributo onde a anotação
* foi encontrada e esse valor é guardado em um HashMap, quando todos os
* atributos forem lidos e todas as annotations também, são disparadas as
* Threads para cada anotação ou conforme configuração do framework.
*
* @author danilo barsotti
*/
public class Validators extends Control {

private Configuration mainConf;

public Validators(Configuration conf){
if(conf.getMapConf() == null){
mainConf = conf.initDefaultConfiguration();
}else{
mainConf = conf.getInstance();
}
}

/**
* Inicia o gerenciamento dos validadores ( threads )
* disparando uma thread para cada annotation ou 1 thread
* para todas as annotations ou um numero especifico de threads
* conforme a configuração do framework.
*
* @author Danilo Barsotti
*/
public void initValidators(Object annotedObject) {
ArrayList<ValidatorException> exceptions = new ArrayList<ValidatorException>();
// Map<nomeDaAnotação, HashMap<nomeDoAtributo, valorDoAtributo>>
Map<String, HashMap<String, Object>> mapFieldUsedAnnotation = new HashMap<String, HashMap<String, Object>>();

Map<String, HashMap<String, Annotation[]>> mapFieldColAnnotation = new HashMap<String, HashMap<String, Annotation[]>>();

List<String> nameAnnotations = new ArrayList<String>();
// pool de threads
ExecutorService pool = Executors.newCachedThreadPool();
Validator validator = null;

Class<Annotation> anottation = null;

Field[] fields = annotedObject.getClass().getDeclaredFields();

Annotation[] colAnnotations = null;

for (Field field : fields) {
colAnnotations = field.getDeclaredAnnotations();
// Separa os atributos para cada anotação que esta sendo usado
for (Annotation annotation : colAnnotations) {
// Verifica se já existe no map algum atributo que já tenha
// usado a anotação
if (mapFieldUsedAnnotation.containsKey(annotation
.annotationType().getSimpleName())) {
mapFieldUsedAnnotation.get(
annotation.annotationType().getSimpleName()).put(
field.getName(),
super.getValueAtribute(super.getMethodName(field
.getName()), annotedObject.getClass()));
// acrescenta no map todas as anotações usadas pelo
// atributo.
mapFieldColAnnotation.get(
annotation.annotationType().getSimpleName()).put(
field.getName(), colAnnotations);
// Nenhum atributo usou a anotação até agora, cria um novo
// map e adiciona o valor
} else {
mapFieldUsedAnnotation.put(annotation.annotationType()
.getSimpleName(), new HashMap<String, Object>());
mapFieldUsedAnnotation.get(
annotation.annotationType().getSimpleName()).put(
field.getName(),
super.getValueAtribute(super.getMethodName(field
.getName()), annotedObject.getClass()));
nameAnnotations.add(annotation.annotationType()
.getSimpleName());
// Todas as anotações usadas pelo atributo.
mapFieldColAnnotation.put(annotation.annotationType()
.getSimpleName(),
new HashMap<String, Annotation[]>());
mapFieldColAnnotation.get(
annotation.annotationType().getSimpleName()).put(
field.getName(), colAnnotations);
}
}
}
// Inicia as Threads
try {
for (String annotationTemp : nameAnnotations) {
// TODO mudar o valor default de onde se encontram os validator, talvez pegar de um .properties
// pega a thread com base no nome da annotation
validator = (Validator) Class.forName(
“control.validator.validators.Validator”
+ annotationTemp).newInstance();
// pega uma instancia da classe que representa a anotação
anottation =  (Class<Annotation>) Class.forName(“annotations.” + annotationTemp);
// seta um map com os valores dos atributos a serem validados
validator.setObjToValidade(mapFieldUsedAnnotation
.get(annotationTemp));
// seta um map com uma coleção de anotações usadas pelo field
validator.setMapFieldColAnnotation(mapFieldColAnnotation
.get(annotationTemp));
// seta uma instancia da anotação
validator.setClazz(anottation);
// seta onde as exceptions serão armazenadas
validator.setExceptions(exceptions);
// seta quais são os campos a serem validados
validator.setFieldsToValidade();
// inicia um validador pegando uma Thread do pool
pool.execute(validator);
}
pool.shutdown();

while(true){
if(pool.isTerminated()){
System.out.println(“acabou!!!”);
break;
}
}
} catch (ClassNotFoundException e) {
// TODO Lançar a exeção correta
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Lançar a exeção correta
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Lançar a exeção correta
e.printStackTrace();
}
}

}

/**
* Classe responsavel por métodos utilitarios em que todas
* as classes que controlam as annotations devem usar.
*
* @author Danilo Barsotti
*/
public abstract class Control {

public abstract void initValidators(Object annotedObject);

/**
* Retorna o nome do método acessor do atributo seguindo o
* padrão JavaBean ( getCamelCase ).
*
* @param atributeName – nome do atributo
* @return nomenclatura do método acessor do atributo, seguindo o padrão JavaBean
*
* @author Danilo Barsotti
*/
protected String getMethodName(String atributeName) {
return “get”+atributeName.replaceFirst(atributeName.substring(0,1), atributeName.substring(0,1).toUpperCase());
}

/**
* Invoca o método usando reflection para pegar o valor de
* retorno.
*
* @param methodName – nome do método a ser invocado
* @param clazz – a qual classe o método pertence
* @return return do método acessor.
*
* @author Danilo Barsotti          */
protected Object getValueAtribute(String methodName, Class clazz) {
Object objReturn = null;
try {
if(!clazz.getMethod(methodName).isAccessible()){
clazz.getMethod(methodName).setAccessible(true);
}
Method method = clazz.getMethod(methodName);
objReturn = method.invoke(clazz.newInstance());
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return objReturn;
}
}

Pronto, o motor de gerenciamento já está pronto, como podem ver é bem simples, ele dispara uma thread para cada anotação diferente encontrada nas classes anotadas.

Agora vamos ver uma anotação.

/**
* Verifica se o atributo anotado está nulo.
* Caso o parametro <b>acceptEmpty</b> seja false, não aceita
* uma String vazia “”, <b>valor default é true</>
*
* @param acceptEmpty – <b>Opcional</b>
* @param message – a mensagem que sera exibida caso a validação
* seja incorreta.
*
* @author danilo barsotti
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {

boolean acceptEmpty() default true;
String message() default “”;

}

Essa anotação verifica se a variavel ( field ) está nula e se for uma String pergunta se aceita vazio (“”) ou não.

Usando convenção ao invez de configuração, a classe que representa a lógica dessa anotação deve ter o nome no seguinte padrão: Validator + O nome da anotação, então ficaria ValidatorNotNull, com isso conseguimos fazer varias anotações sem precisar configurar nada, somente precisamos fazer 1 annotation e 1 classe que representa sua lógica de validação e então o framework já vai conseguir usa-la.

A classe que representa a regra de validação da anotação:

/**
* Verifica se o atributo é Null
*
* @author danilo barsotti
*/
public class ValidatorNotNull extends Validator{

@Override
public void startValidation() throws ValidatorException  {
for(String fieldName : fields){
super.setFieldName(fieldName);
if(isNull(fieldValue)){
System.out.println(“O atributo ” + fieldName + ” é nulo!”);
}else{
if(getBooleanValueMethodAnnotation(fieldName, “acceptEmpty”)){
System.out.println(“O atributo ” + fieldName + ” Não é nulo! o valor dele é = ” + getValueToValidade(fieldName));
}else{
if(fieldValue.equals(“”)){
System.out.println(“O atributo ” + fieldName + ” Não é nulo mas é uma String vazia”);
addValidationError(fieldName);
}
}
}
}
}

/**
* Verifica se o objeto é nulo
*
* @param objeto a ser verificado
* @return boolean
*
* @author danilo barsotti
*/
public boolean isNull(Object obj){
return obj == null;
}
}

Como podem ver, toda classe que representa uma anotação deve estender a classe Validator, ela é uma classe que implementa Runnable e também disponibiliza alguns métodos utilitarios.

Classe Validator

/**
* Classe que todos os outros validadores devem estender
* pois é por ele que a thread acessa os atributos a serem
* validados de acordo com a annotation que a thread representa.
*
* @author danilo barsotti
*
*/
public abstract class Validator implements Runnable{
//Logger logger;
Object lock1 = new Object();
private String fieldName;
public Object fieldValue;
public String[] fields;
/**
* Classe que representa uma anotação.
*/
private Class<Annotation> clazz;
/**
* Onde todas as exceptions de validação são guardadas
*/
ArrayList<ValidatorException> exceptions;
/**
* Guarda os valores dos atributos a serem validados
*/
private HashMap<String, Object> objToValidade;

/**
* Guarda todas as anotações usadas pelo atributo
*/
private HashMap<String, Annotation[]> mapFieldColAnnotation;
/**
* Método que inicia a validação
*
* @author danilo barsotti
* @throws ValidatorException
* @throws ValidatorException
*/
public abstract void startValidation() throws ValidatorException;

/**
* Seta a anotação que é usada pelo validador.
*
* @param clazz – Classe que representa a anotação
*/
public void setClazz(Class<Annotation> clazz) {
this.clazz = clazz;
}

/**
* Retorna os nomes dos campos que foram anotados.
*
* @return keys
*
* @author danilo barsotti
*/
public String[] getFieldsToValidade(){
String[] keys = new String[objToValidade.keySet().size()];
Iterator<String> iter = objToValidade.keySet().iterator();
int cont = 0;
while(iter.hasNext()){
keys[cont] = iter.next().toString();
cont++;
}
return keys;
}
/**
* ESSE MÉTODO NÃO DEVE SER SOBRESCRITO E NÃO DEVE SER USADO.
* É USADO SOMENTE PELO FRAMEWORK.
* <br>
* Seta os nomes dos atributos a serem validados.
*/
public void setFieldsToValidade(){
this.fields = getFieldsToValidade();
}
public void addValidationError(String fieldName){
synchronized (lock1) {
exceptions.add(new ValidatorException(fieldName));
}
}
/**
* Retorna um HashMap que contem os objetos a serem validados
*
* @return HashMap com os objetos
*
* @author danilo barsotti
*/
public HashMap<String, Object> getObjToValidade() {
return objToValidade;
}
/**
* Retorna o valor do atributo a ser validado
*
* @param key – Nome do atributo (field)
*
* @return object – valor retornado pelo método
*/
public Object getValueToValidade(String key) {
return getObjToValidade().get(key);
}
/**
* ESSE MÉTODO NÃO DEVE SER SOBRESCRITO E NÃO DEVE SER USADO.
* É USADO SOMENTE PELO FRAMEWORK.
*
* Seta os objetos a serem validados
*
* @param objToValidade – map com os objetos a serem validados
*
* @author danilo barsotti
*/
public void setObjToValidade(HashMap<String, Object> objToValidade) {
this.objToValidade = objToValidade;
}

/**
* ESSE MÉTODO NÃO DEVE SER SOBRESCRITO E NÃO DEVE SER USADO.
* É USADO SOMENTE PELO FRAMEWORK.
*
* Da inicio a Thread de validação conforme a implementação do validador.
*
* @author danilo barsotti
*/
public void run(){
try{
startValidation();
}catch(ValidatorException ex){
synchronized (lock1) {
exceptions.add(ex);
}
}
}

/**
* Retorna a anotações que foi usada pelo field.
*
* @param fieldName – nome do field
*
* @return Annotation
*
* @author danilo barsotti
*/
public Annotation getAnnotation(String fieldName) {
Annotation[] annotations = mapFieldColAnnotation.get(fieldName);
for(Annotation annotation : annotations){
if(annotation.annotationType().getSimpleName().equals(clazz.getSimpleName())){
return annotation;
}
}
// TODO lançar uma excessão aqui, pois não encontrou a anotação!
//logger.log(Level.WARNING, “asdf”);
return null;
}
/**
* Usado para pegar o valor (Integer) de retorno do método que foi declarado
* na anotação
*
* @param fieldName – nome do atributo anotado
* @param nameMethod – nome do método na anotação
*
* @return Integer com o valor retornado pelo método na anotação
*
* @author danilo barsotti
*/
public Integer getIntegerValueMethodAnnotation(String fieldName, String nameMethod){
// TODO verificar se o clazz é uma instancia de Annotation.class
return  (Integer) getMethodValue(fieldName, nameMethod);
}

/**
* Usado para pegar o valor (boolean) de retorno do método que foi declarado
* na anotação
*
* @param fieldName – nome do atributo anotado
* @param nameMethod – nome do método na anotação
*
* @return boolean com o valor retornado pelo método na anotação
*
* @author danilo barsotti
*/
public boolean getBooleanValueMethodAnnotation(String fieldName, String nameMethod){
return (Boolean) getMethodValue(fieldName, nameMethod);
}
/**
* Usado para pegar o valor (boolean) de retorno do método que foi declarado
* na anotação
*
* @param fieldName – nome do atributo anotado
* @param nameMethod – nome do método na anotação
*
* @return boolean com o valor retornado pelo método na anotação
*
* @author danilo barsotti
*/
public String getStringValueMethodAnnotation(String fieldName, String nameMethod){
return (String) getMethodValue(fieldName, nameMethod);
}
/**
* Usado para pegar um valor de retorno de um método que foi declarado
* na anotação, pega qualquer valor de retorno mas necessita de cast
* pois retorna um object.
*
* @param fieldName – nome do atributo anotado
* @param nameMethod – nome do método na anotação
*
* @return object – valor retornado pelo método
*/
private Object getMethodValue(String fieldName, String nameMethod){
// TODO verificar se o clazz é uma instancia de Annotation.class
Object obj = null;
try {
Annotation annotation = getAnnotation(fieldName);
obj = annotation.getClass().getMethod(nameMethod).invoke(annotation);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return obj;
}

/**
* ESSE MÉTODO NÃO DEVE SER SOBRESCRITO E NÃO DEVE SER USADO.
* É USADO SOMENTE PELO FRAMEWORK.
*
* Seta um map que contem as anotações usadas pelos fields.
*
* @param objToValidade – map com as anotações
*
* @author danilo barsotti
*/
public void setMapFieldColAnnotation(
HashMap<String, Annotation[]> mapFieldColAnnotation) {
this.mapFieldColAnnotation = mapFieldColAnnotation;
}

public ArrayList<ValidatorException> getExceptions() {
return exceptions;
}

public void setExceptions(ArrayList<ValidatorException> exceptions) {
this.exceptions = exceptions;
}

public void setFieldName(String fieldName) {
this.fieldName = fieldName;
this.fieldValue = getValueToValidade(fieldName);
//this.fields = getFieldsToValidade();
}
}

Quando o validador encontra algo que não é aceito, ele lança uma exeção e coloca ela dentro de um array.

Segue a classe que representa uma exeção de validação:

public class ValidatorException extends Exception{

String field;
public ValidatorException(String field){
System.out.println(“—– rolou uma exception no campo = ” + field + ” —–“);
this.field = field;
}
public void teste(){
}
}

Também foi feito uma classe para fazer algumas configurações na framework, também não posso mostrar muito dela então tirei varias coisas bacanas, mas vamos talvez no proximo artigo focar nela e também em AOP, para deixar nossa framework mais interessante.

Classe de configuração:

public class Configuration {

private static Configuration config;
private static Map<String, String> mapConf;
private Configuration(){}
public static Configuration getInstance(){
if(config == null){
config = new Configuration();
mapConf = new HashMap<String, String>();
}
return config;
}
public static Configuration setConfig(PropertiesConfiguration conf, String value){
getInstance();
mapConf.put(conf.toString(), value);
return config;
}
public static String getConf(PropertiesConfiguration key){
return mapConf.get(“”);
}
public static Map getMapConf(){
return mapConf;
}
public static Configuration initDefaultConfiguration(){
getInstance().getMapConf().put(PropertiesConfiguration.Threads, “”);
return getInstance();
}
}

E uma enum com as propriedades da configuração, só tem uma configuração pelo mesmo motivo já citado diversas vezes.

Classe PropertiesConfiguration:

public enum PropertiesConfiguration {

Threads;

}

Com isso finalizamos nossa framework, agora para testar basta anotar alguma classe e chamar o método de validação, no banco fiz isso ficar automatico, sem precisar chamar nenhum método a framework já verifica se tem algo de errado, lança suas exeções, joga tudo na request e exibe em uma pagina que também é configuravel, mas isso é para um próximo artigo.

Para testar vamos anotar um VO:

public class TesteVo {

@NotNull(acceptEmpty=false)
public String testeNotNull = “”;
@NotNull
public String testeNotNull2 = null;
public String testeNotNull3 = “valorTesteNotNull3”;
@NotNull
public String range = “valorRange”;
@NotNull
public Integer testeNotNull4;
@NotNull
public Integer testeNotNull5 = 0;
@NotNull
public Integer testeNotNull6 = 211;
@NotNull
public int testeNotNull7 = 1;
@NotNull
public Object testeNotNull8 = null;

public String getTesteNotNull() {
return testeNotNull;
}

public void setTesteNotNull(String testeNotNull) {
this.testeNotNull = testeNotNull;
}

public String getRange() {
return range;
}

public void setRange(String range) {
this.range = range;
}

public String getTesteNotNull2() {
return testeNotNull2;
}

public void setTesteNotNull2(String testeNotNull2) {
this.testeNotNull2 = testeNotNull2;
}

public String getTesteNotNull3() {
return testeNotNull3;
}

public void setTesteNotNull3(String testeNotNull3) {
this.testeNotNull3 = testeNotNull3;
}

public Integer getTesteNotNull4() {
return testeNotNull4;
}

public void setTesteNotNull4(Integer testeNotNull4) {
this.testeNotNull4 = testeNotNull4;
}

public Integer getTesteNotNull5() {
return testeNotNull5;
}

public void setTesteNotNull5(Integer testeNotNull5) {
this.testeNotNull5 = testeNotNull5;
}

public Integer getTesteNotNull6() {
return testeNotNull6;
}

public void setTesteNotNull6(Integer testeNotNull6) {
this.testeNotNull6 = testeNotNull6;
}

public int getTesteNotNull7() {
return testeNotNull7;
}

public void setTesteNotNull7(int testeNotNull7) {
this.testeNotNull7 = testeNotNull7;
}

public Object getTesteNotNull8() {
return testeNotNull8;
}

public void setTesteNotNull8(Object testeNotNull8) {
this.testeNotNull8 = testeNotNull8;
}
}

E chamar a validação para testar o resultado:

public class AnnotationsTestMain {

public static void main(String args[]){
Control teste = new Validators(Configuration.setConfig(PropertiesConfiguration.Threads, “1”));
TesteVo t = new TesteVo();
teste.initValidators(t);
}

}

A saida no console foi:

O atributo testeNotNull4 é nulo!
O atributo testeNotNull6 Não é nulo! o valor dele é = 211
O atributo testeNotNull Não é nulo mas é uma String vazia
—– rolou uma exception no campo = testeNotNull —–
O atributo testeNotNull5 Não é nulo! o valor dele é = 0
O atributo range Não é nulo! o valor dele é = valorRange
O atributo testeNotNull2 é nulo!
O atributo testeNotNull7 Não é nulo! o valor dele é = 1
O atributo testeNotNull8 é nulo!
acabou!!!

Com isso chegamos ao fim da nossa pequena mas pratica framework de validação, os proximos passos são, torna-la mais configuravel, fazer mais validadores, usar AOP, colocar as exeções na request para ser exibidas em um jsp e por ultimo junta-la com a framework Apache Wicket.

Não estou tendo tempo de postar, esta muito corrido meu projeto, mas vou ver se consigo arrumar tempo.

Um grande abraço a todos, e até a proxima!!! :- )

4 Respostas to “O poder das annotations em sistemas legados”

  1. O poder das annotations em sistemas legados « Java for Dinner

    Artigo que mostra o poder das anotações em sistemas legados, tornando coisas que seriam trabalhosas e tediosas em praticas e prazerosas.

  2. hum, Hibernate Validator?

  3. Danilo Barsotti said

    Tiago Albineli Motta, Sim e Não…
    Já conhecia o hibernate validator antes de fazer esse projeto e uma das coisas que acabei esquecendo de comentar no artigo foi que eu não podia usar nenhuma framework externa ( que não foi feita por mim ou pela equipe ) nesse projeto, então dai a necessidade de fazer uma versão “Brasileira” do hibernate validator.
    Masss, quando comecei a fazer essa framework uma das idéias era de não fazer algumas coisas como o hibernate validator, como por exemplo aquela herança entre a anotações, eu detesto aquilo, acho feio e pouco intuitivo.
    E ao meu ver, oque eu mostrei no artigo ficou até mais flexível doque no hibernate validador, compare a facilidade de se fazer um validador nessa framework e no hibernate validador…

    Tiago, obrigado pela sua participação e espero por muitas outras participações!

  4. Joao Paraiso said

    Ola Danilo boa tarde,
    Parabens pela iniciativa e pelo belo codigo escrito acima, atualmente em meu trabalho tambem nao posso utilizar frameworks externos e logo terei que desenvolver algo para utilizar nos projetos, e tenho certeza que encontrei alguem pra me dar muitas aulas “dicas”. Em relacao ao Hibernate Validator, o seu gerenciador de validacoes ficou bem mais simples de se entender e principalmente mais flexivel.

    Novamente Parabens pela iniciativa.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

 
%d blogueiros gostam disto: