segunda-feira, 17 de maio de 2010

Apache Commons - BeanUtils

Primeiramente gostaria de me desculpar pela demora na atualização do blog. Muito trabalho, graças a Deus, mas vou me esforçar para que isso não aconteça novamente.
Apache commons é um projeto apache dedicado a criação de componentes reutilizáveis. Mas isto, é claro, você já sabia. Inauguro hoje uma série de posts cujo objetivo é se aprofundar nestes componentes expondo as funcionalidades disponibilizadas, cujos detalhes são ignorados pela maioria dos desenvolvedores.
O projeto é dividido em uma variedade de categorias de componentes (FileUpload, Lang, Net, Collections, etc..). Este post em particular irá se concentrar em apenas uma: BeanUtils. Primeiramente, no entanto, vale reforçar dois conceitos importantes: reflexão e java beans.
Reflexão, Introspecção e Intercessão
Existe alguma confusão a respeito destes termos na comunidade (em especial em relação aos dois primeiros). Sendo rápido e rasteiro, reflexão é a habilidade de uma linguagem de manipular, em tempo de execução, elementos de suas própria estrutura, como classes, métodos e propriedades. Introspecção é um tipo especial de reflexão limitada a examinar estes elementos, sem alterá-los. Intercessão seria justamente o outro lado da moeda: alteração em tempo de execução de classes e métodos dos objetos (http://goo.gl/dMZL).
A pesar do nome do pacote (java.lang.reflect), o que java nos oferece é na verdade apenas introspecção. Como veremos adiante a biblioteca BeanUtils tenta nos fornecer uma forma básica de intercessão.
Java Beans
Java beans são um tipo especial de classe, que segue um padrão de nomenclatura definido em uma especificação (http://goo.gl/3fGL). Só isso :P.
BeanUtils
BeanUtils é uma biblioteca que alia os conceitos de introspecção e java Beans, disponibilizando componentes que provêem:
  • Acesso a propriedades e métodos de java beans de forma facilitada, incluindo conversão de tipos integrada.
  • Definição dinâmica de beans, uma forma básica de intercessão.
Acesso a Propriedades via Introspecção
Para os exemplos, vamos considerar as classes JavaBean e AnotherJavaBean aderentes aos padrões JavaBeans:


package main;

import java.util.List;

public class JavaBean {
 
 private String stringProp;
 
 private Integer intProp;
 
 private List listProp;
 
 private AnotherJavaBean anotherJavaBeanProp;


 public AnotherJavaBean getAnotherJavaBeanProp() {
  return anotherJavaBeanProp;
 }

 public void setAnotherJavaBeanProp(AnotherJavaBean anotherJavaBeanProp) {
  this.anotherJavaBeanProp = anotherJavaBeanProp;
 }

 public String getStringProp() {
  return stringProp;
 }

 public void setStringProp(String stringProp) {
  this.stringProp = stringProp;
 }

 public Integer getIntProp() {
  return intProp;
 }

 public void setIntProp(Integer intProp) {
  this.intProp = intProp;
 }

 public List getListProp() {
  return listProp;
 }

 public void setListProp(List colProp) {
  this.listProp = colProp;
 }

}


package main;

public class AnotherJavaBean {

 private Short shortProp;

 public Short getShortProp() {
  return shortProp;
 }

 public void setShortProp(Short shortProp) {
  this.shortProp = shortProp;
 }

}





Para obter o valor da propriedade “stringProp” de uma instancia da classe JavaBean através do seu método getter usando a API Java é preciso fazer algo parecido com o código das linhas abaixo:

// Criando uma instancia para ser acessada dinamicamente
  JavaBean beanTeste = new JavaBean();
  beanTeste.setStringProp("String de teste");

  // Nome do metodo getter. Em um cenário mais generalista, esta string
  // teria que ser montada dinamicamente.
  String nomeGetter = "getStringProp";

  // Obtendo o class da classe a ser manipulada.
  Class clazz = JavaBean.class;

  try {

   // Obtendo instância da classe Method para o método a ser invocado.
   Method stringPropGetter = clazz.getMethod(nomeGetter);

   // O método invoke da classe Method executa o método, em uma
   // instancia e com argumentos passados por parâmetro. Neste caso não
   // há argumentos.
   String valorPropriedade = (String) stringPropGetter
     .invoke(beanTeste);

   // Imprimindo no console o valor da propriedade.
   System.out.println(valorPropriedade);

  } catch (SecurityException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (NoSuchMethodException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IllegalArgumentException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InvocationTargetException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

 

Usando o componente PropertyUtils para executar a mesma tarefa, o código fica assim:

// Criando uma instancia para ser acessada dinamicamente
  JavaBean beanTeste = new JavaBean();
  beanTeste.setStringProp("String de teste");

  //Usa-se o nome da propriedade. O nome do getter é derivado internamente pela API.
  String nomePropriedade = "stringProp";

  try {
   
   //Obtendo o valor da propriedade. Basta passar o objeto e o nome da propridade.
   String valorPropriedade = (String) PropertyUtils.getProperty(beanTeste,
     nomePropriedade);
   
   // Imprimindo no console o valor da propriedade.
   System.out.println(valorPropriedade);
   
  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InvocationTargetException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (NoSuchMethodException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }



Além do código mais compacto, vale observar a quantidade reduzida de exceções a serem tratadas, e o fato do componente tomar vantagem dos padrões de nomenclatura Java Bean para derivar automaticamente o nome do método getter para a propriedade requisitada.
Além de acesso a propriedades simples, o componente PropertyUtils fornece facilidades no acesso a propriedades indexadas:

// Criando uma instancia para ser acessada dinamicamente
  JavaBean beanTeste = new JavaBean();
  List list = Arrays.asList(1, 2, 3, 4, 5);
  beanTeste.setListProp(list);

  //Usa-se o nome da propriedade. O nome do getter é derivado internamente pela API.
  String nomePropriedade = "listProp";

  try {
   // Obtendo o valor da propriedade. Aqui passamos o objeto, o nome da
   // propriedade e indice a ser acessado.
   Integer valorPropiedade = (Integer) PropertyUtils
     .getIndexedProperty(beanTeste, nomePropriedade, 3);

   // Imprimindo no console o valor da propriedade.
   System.out.println(valorPropiedade);

  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InvocationTargetException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (NoSuchMethodException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }


e aninhadas:

// Criando uma instancia para ser acessada dinamicamente
  JavaBean beanTeste = new JavaBean();
  AnotherJavaBean anotherBeanTest = new AnotherJavaBean();
  anotherBeanTest.setShortProp((short) 1);
  beanTeste.setAnotherJavaBeanProp(anotherBeanTest);

  // Usa-se o nome da propriedade. O nome do getter é derivado
  // internamente pela API.
  String nomePropriedade = "anotherJavaBeanProp.shortProp";
  
  try {
   Short valorPropriedade = (Short) PropertyUtils.getNestedProperty(beanTeste, nomePropriedade);
   
   System.out.println(valorPropriedade);
  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InvocationTargetException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (NoSuchMethodException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }



Além de acessar, é possível atribuir valores as propriedades dos objetos, de forma muito semelhante a usada para acessar os dados (inclusive para propriedades indexadas e aninhadas.)
Outra funcionalidade interessante, fornecida pelo componente BeanUtils é copia de propriedades entre objetos. Através dos métodos copyProperties e copyProperty é possível copiar o valor de uma ou todas as propriedades de nome semelhante entre beans, mesmo que sejam de classes não relacionadas.

JavaBean oneBean = new JavaBean();
  JavaBean otherBean = new JavaBean();

  oneBean.setIntProp(1);
  oneBean.setStringProp("string");
  oneBean.setListProp(Arrays.asList(0, 1, 2));
  
  try {
   BeanUtils.copyProperties(otherBean, oneBean);
   
   System.out.println(otherBean.getStringProp());
   System.out.println(otherBean.getIntProp());
   System.out.println(otherBean.getListProp());
  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InvocationTargetException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  


Falando do componente BeanUtils, vale ressaltar ele contém métodos de acesso e atribuição de propriedade semelhantes a PropertyUtils, incluindo conversão automática. A biblioteca vem com converters próprios mais é possível cadastrar os seus próprios de maneira simples.

public class BeanUtilsTest {

 //Classe que fará a conversão para um AnotherJavaBean
 public static class AnotherJavaBeanConverter implements Converter {

  //Método recebe a classe para a qual será feita a conversão e o valor a ser convertido.
  @Override
  public Object convert(Class arg0, Object arg1) {

   AnotherJavaBean bean = new AnotherJavaBean();
   bean.setShortProp(Short.valueOf(arg1.toString()));

   return bean;
  }

 }

 /**
  * @param args
  */
 public static void main(String[] args) {
  //criando instancia a ser populada dinamicamente
  JavaBean beanTest = new JavaBean();
  
  //registrando converter para o AnotherJavaBean
  ConvertUtils.register(new AnotherJavaBeanConverter(),
    AnotherJavaBean.class);

  try {
   // atribuindo valor dinaminamicamente. a String "1" será convertida para Integer usando converters padrão da biblioteca
   BeanUtils.setProperty(beanTest, "intProp", "1");
   
   //atribuindo valor dinaminamicamente. o valor inteiro 123 será convertido usando converters padrão da biblioteca 
   BeanUtils.setProperty(beanTest, "stringProp", 123);
   
   //atribuindo valor dinaminamicamente. o valor inteiro 2 será convertido usando converter registrado para a classe AnotherJavaBean
   BeanUtils.setProperty(beanTest, "anotherJavaBeanProp", 2);

   System.out.println(beanTest.getIntProp());
   System.out.println(beanTest.getStringProp());
   System.out
     .println(beanTest.getAnotherJavaBeanProp().getShortProp());
  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InvocationTargetException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

 }
}


Beans Dinâmicos
Beans dinâmicos ou DynaBeans possibilitam uma forma básica de intercessão: ou seja mudança dinâmica na estrutura (propriedades) dos beans.
Segue um exemplo onde criamos o mesmo bean JavaBean usado  nos exemplos anteriores de forma dinâmica:

String nomeStringProp = "stringProp";
  String nomeIntProp = "intProp";
  String nomeListProp = "listProp";
  String nomeAnotherJavaBeanProp = "anotherJavaBeanProp";
  String shortPropEmAnotherJavaBean = "anotherJavaBeanProp.shortProp";

  // criando um vetor de DynaProperty, que representa uma propriedade de
  // um DynaBean
  DynaProperty[] props = new DynaProperty[] {
    new DynaProperty(nomeStringProp, String.class),
    new DynaProperty(nomeIntProp, Integer.class),
    new DynaProperty(nomeListProp, List.class),
    new DynaProperty(nomeAnotherJavaBeanProp, AnotherJavaBean.class) };

  // Criando uma classe de DynaBeans, chamada 'JavaBean' com as
  // propriedades definidas a cima.
  BasicDynaClass dynaClass = new BasicDynaClass("JavaBean", null, props);

  try {

   // Criando uma instância de JavaBean
   DynaBean javaBeanTest = dynaClass.newInstance();

   // Atribuindo valores as propriedades
   javaBeanTest.set(nomeStringProp, "string Teste");
   javaBeanTest.set(nomeIntProp, 200);
   javaBeanTest.set(nomeListProp, Arrays.asList(1, 2, 3, 4));
   AnotherJavaBean anotherJavaBean = new AnotherJavaBean();
   anotherJavaBean.setShortProp((short) 13);
   javaBeanTest.set(nomeAnotherJavaBeanProp, anotherJavaBean);

   // imprimindo o valor dos atributos. Note que os métodos de acesso
   // dinâmico funcionam como esperado mesmo com DynaBeans.
   System.out.println(PropertyUtils.getSimpleProperty(javaBeanTest,
     nomeStringProp));
   System.out.println(PropertyUtils.getSimpleProperty(javaBeanTest,
     nomeIntProp));
   System.out.println(PropertyUtils.getSimpleProperty(javaBeanTest,
     nomeListProp));
   System.out.println(PropertyUtils.getNestedProperty(javaBeanTest,
     shortPropEmAnotherJavaBean));

  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InstantiationException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InvocationTargetException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (NoSuchMethodException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

Vale observar o uso das classes DynaClass (através de sua subclasse BasicDynaClass) e DynaBean. Outro ponto importante é o uso indistinto dos métodos de acesso dinâmico que vimos anteriormente.
Além de DynaBean básicos (que seriam os do exemplo anterior) a biblioteca disponibiliza outros tipos de DynaBeans, como por exemplo os ResultSetDynaBeans e RowSetDynaBeans. A variedade mais interessante de DynaBean, no entanto, é o LazyDynaBean. A principal característica deste componente é a criação de propriedades no momento da atribuição (não há necessidade de fazer isso previamente).

String nomeStringProp = "stringProp";
  String nomeIntProp = "intProp";
  String nomeListProp = "listProp";
  String nomeAnotherJavaBeanProp = "anotherJavaBeanProp";
  String shortPropEmAnotherJavaBean = "anotherJavaBeanProp.shortProp";

  DynaBean javaBeanTest = new LazyDynaBean();

  //Criando e atribuindo valores as propriedades
  javaBeanTest.set(nomeStringProp, "string Teste");
  javaBeanTest.set(nomeIntProp, 200);
  javaBeanTest.set(nomeListProp, Arrays.asList(1, 2, 3, 4));
  AnotherJavaBean anotherJavaBean = new AnotherJavaBean();
  anotherJavaBean.setShortProp((short) 13);
  javaBeanTest.set(nomeAnotherJavaBeanProp, anotherJavaBean);

  try {
   // imprimindo o valor dos atributos. Note que os métodos de acesso
   // dinâmico funcionam como esperado mesmo com DynaBeans.
   System.out.println(PropertyUtils.getSimpleProperty(javaBeanTest,
     nomeStringProp));
   System.out.println(PropertyUtils.getSimpleProperty(javaBeanTest,
     nomeIntProp));
   System.out.println(PropertyUtils.getSimpleProperty(javaBeanTest,
     nomeListProp));
   System.out.println(PropertyUtils.getNestedProperty(javaBeanTest,
     shortPropEmAnotherJavaBean));
  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InvocationTargetException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (NoSuchMethodException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }



Conclusão
Estes são, portanto, os componentes mais importante da biblioteca BeanUtils da apache commons. Esperem em breve por outros posts a respeito de bibliotecas co-irmãs!





















Nenhum comentário:

Postar um comentário