Criando um Lazy Loading Remoto com Annotations e APT

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (0)  (0)

O artigo visa explorar o uso dos annotations e do APT para criar um solução Lazy Loading para as entidades que são detachada do container Java EE. OBS: Este artigo deu origem a um produto opensource chamado DataSlim e que pode ser baixado em http://code.google.com/p/dataslim/

Lazy Loading é o mecanismo utilizado pelos frameworks de persistência para carregar informaçöes sobre demanda. Esse mecanismo torna as entidades mais leves, pois suas associaçöes säo carregadas apenas no momento em que o método que disponibiliza o dado associativo é chamado. No JPA (Java Persistence API), todo atributo associativo de uma entidade é por padräo Lazy, podendo ser alterado para Eager através dos annotations @Basic, @ManyToOne, @OneToOne, @OneToMany, @ManyToMany. Quando definimos o fetch para Eager, estamos dizendo para o framework de persistência carregar além da entidade desejada, também o relacionamento que ela possui com o atributo annotado. Neste artigo, vamos tratar especificamente do carregamento Lazy quando a entidade é acessada fora do container, onde näo existe escopo transacional.

 Para ficar claro porque é necessário um tratamento especial para o caso onde a entidade é acessada fora do escopo transacional, vamos fazer um acesso bem simples a uma entidade que possui um relacionamento com outra, e ver o que acontece quando fazemos acesso ao método “get” que retorna a entidade associativa.

Cenário

 O cenário proposto é de criar um cadastro simples de endereço, onde o mesmo terá um relacionamento “muitos-para-um” com o tipo de logradouro.Quando um endereço for pesquisado, sua entidade só poderá carregar sua associaçäo com tipo de logradouro, quando o usuário acessar o método getTipoLogradouro.

Relacionamento muitos-para-um entre EnderecoBean e TipoLogradouroBean

RelacionamentoEntidades.jpg

 Vamos criar as classes de persistência EnderecoBean e TipoLogradouroBean.

 Näo é escopo deste artigo entrar em detalhes sobre os Annotations do JPA.

 Para isso visite o site http://jcp.org/en/jsr/detail?id=220.

Classe TipoLogradouroBean.java

package com.artigo.apt.persistence;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;@Entity(name="TipoLogradouroBean")
@Table(name="TIPO_LOGRADOURO")
public class TipoLogradouroBean implements Serializable{
 private Long id;
 private String tipo;
 
 @Id
 @TableGenerator(
  name="TABLE_SEQUENCE_GENERATOR",
  table="TABLE_SEQUENCE_GENERATOR",
  pkColumnName="GEN_KEY",
  valueColumnName="GEN_VALUE",
  pkColumnValue="tipo_logradouro_id",
  allocationSize=1
 )
 @GeneratedValue(strategy=GenerationType.TABLE,
  generator="TABLE_SEQUENCE_GENERATOR")
 @Column(name="ID")
 public Long getId() {
  return id;
 } public void setId(Long id) {
 this.id = id;
 } public void setTipo(String tipo){
  this.tipo = tipo;
 } public String getTipo(){
  return tipo;
 }
}

Classe EnderecoBean.java

 

package com.artigo.apt.persistence;

 

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

 

@Entity(name="EnderecoBean")
@Table(name="ENDERECO")
public class EnderecoBean implements java.io.Serializable{
 private Long id;
 private String endereco;
 private String complemento;
 private TipoLogradouroBean tipoLogradouro;
 
 @Id
 @TableGenerator(
  name="TABLE_SEQUENCE_GENERATOR",
  table="TABLE_SEQUENCE_GENERATOR",
  pkColumnName="GEN_KEY",
  valueColumnName="GEN_VALUE",
  pkColumnValue="endereco_id",
  allocationSize=1
 )
 @GeneratedValue(strategy=GenerationType.TABLE,
  generator="TABLE_SEQUENCE_GENERATOR")
 @Column(name="ID")
 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getEndereco() {
  return endereco;
 }

 public void setEndereco(String endereco) {
  this.endereco = endereco;
 }

 public String getComplemento(){
  return complemento;
 }

 public void setComplemento(String complemento){
  this.complemento = complemento;
 }

 @ManyToOne(optional=false)
 @JoinColumn(name="TIPO_LOGRADOURO_ID", referencedColumnName="ID")
 public TipoLogradouroBean getTipoLogradouro(){
  return tipoLogradouro;
 }

 public void setTipoLogradouro(TipoLogradouroBean tipoLogradouro){
  this.tipoLogradouro = tipoLogradouro;
 }
}

Analisando o código EnderecoBean, vimos que a associaçäo no banco de dados

entre endereço e tipo logradouro será através da coluna TIPO_LOGRADOURO_ID

e que a associaçäo será do tipo muitos-para-um definido pela annotation

@ManyToOne. Por padräo, o framework de persistência já assume que o fetch

será Lazy por ser uma definiçäo do JPA, logo näo é necessário ter o

elemento fetch=FetchType.Lazy no annotation @ManyToOne.

Agora vamos criar os sessions beans que iräo popular as tabelas e fazer as

buscas nas mesmas.

O Session Bean EnderecoFacade será responsável pela inclusäo dos dados de

tipo de logradouro e endereço.

 

Interface IEnderecoFacade.java

 

package com.artigo.apt.interfaces;

import javax.ejb.Remote;
import com.artigo.apt.persistence.EnderecoBean;
import com.artigo.apt.persistence.TipoLogradouroBean;

@Remote
public interface IEnderecoFacade {

 public EnderecoBean persist(EnderecoBean endereco);
 public TipoLogradouroBean persist(TipoLogradouroBean logradouroBean);
}

 

Classe EnderecoFacade.java

 

package com.artigo.apt.ejb;


import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.artigo.apt.interfaces.IEnderecoFacade;
import com.artigo.apt.persistence.EnderecoBean;
import com.artigo.apt.persistence.TipoLogradouroBean;

@Stateless(name="EnderecoFacade")
public class EnderecoFacade implements IEnderecoFacade {


 

   @PersistenceContext(unitName="Artigo")
   EntityManager em;


 
  public EnderecoBean persist(EnderecoBean endereco){
      em.persist(endereco);
      return endereco;
   }


   public TipoLogradouroBean persist(TipoLogradouroBean logradouro){
      em.persist(logradouro);
      return logradouro;
   }
}

 

O Session Bean EnderecoQueryFacade será responsável pelas buscas feitas no

componente Endereço.

 

Classe IEnderecoQueryFacade.java

 

package com.artigo.apt.interfaces;

import javax.ejb.Remote;
import com.artigo.apt.persistence.EnderecoBean;

@Remote
public interface IEnderecoQueryFacade {

 

   public EnderecoBean findEnderecoById(Long id);


 

}

 

Classe EnderecoQueryFacade.java

 

package com.artigo.apt.ejb;


 

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.artigo.apt.interfaces.IEnderecoQueryFacade;
import com.artigo.apt.persistence.EnderecoBean;

 

@Stateless(name="EnderecoQueryFacade")
public class EnderecoQueryFacade implements IEnderecoQueryFacade{
 
 @PersistenceContext(unitName="Artigo")
 EntityManager em;
 public EnderecoBean findEnderecoById(Long id) {
  return em.find(EnderecoBean.class, id);
 }
}

 

Build/Deploy

 

Agora que já temos nosso componente criado, precisamos apenas fazer o

build e deploy no servidor. Neste estaremos usando o jboss-4.0.4.GA no

fedora 4, por consequência disso, estou usando os jars :

/usr/jboss-4.0.4.GA/server/default/lib/ejb3-persistence.jar

/usr/jboss-4.0.4.GA/server/default/deploy/ejb3.deployer/jboss-ejb3x.jar

/usr/jboss-4.0.4.GA/server/default/lib/jboss-j2ee.jar

para fazer a compilaçäo das classes. Para o ciclo de build/deploy, nada

melhor que recorrer ao ant para execuçäo desta tarefa. O build.xml e os

artefatos necessários para o build encontram-se disponíveis para download.

 

Depois de rodar o build.xml o diretório com os pacotes fica assim:

 

Pacotes1.jpg

 

Agora é só copiar o arquivo artigo-apt-core.ear para o diretório de deploy

do jboss /usr/jboss-4.0.4.GA/server/default/deploy e subir o servidor.

 

Console2.jpg

 

 

Agora vamos criar nossa classe cliente.

A classe cliente será responsável por inserir um tipo de logradouro e um

endereço, associando um ao outro. Depois de feita a inclusäo, vamos

recuperar o endereço cadastrado e verificar o retorno do tipo de logradouro

que associamos no cadastro.

 

Classe APTClient.java

 

package com.artigo.apt.client;

 

import javax.naming.InitialContext;

import com.artigo.apt.interfaces.IEnderecoFacade;

import com.artigo.apt.interfaces.IEnderecoQueryFacade;

import com.artigo.apt.persistence.EnderecoBean;

import com.artigo.apt.persistence.TipoLogradouroBean;

 

public class APTClient {

 

   public TipoLogradouroBean persistLogradouro(String tipo)

      throws Exception{

      TipoLogradouroBean tipoLogradouroBean = new TipoLogradouroBean();

      tipoLogradouroBean.setTipo(tipo);

      IEnderecoFacade ief = getEnderecoFacade();

      tipoLogradouroBean = ief.persist(tipoLogradouroBean);

      return tipoLogradouroBean;

   }

 

   public EnderecoBean persistEndereco(TipoLogradouroBean tipoLogradouro, String

      endereco, String complemento)

      throws Exception{

      EnderecoBean enderecoBean = new EnderecoBean();

      enderecoBean.setEndereco(endereco);

      enderecoBean.setComplemento(complemento);

      enderecoBean.setTipoLogradouro(tipoLogradouro);

      IEnderecoFacade ief = getEnderecoFacade();

      enderecoBean = ief.persist(enderecoBean);

      return enderecoBean;

   }

 

   public void test() throws Exception{

      TipoLogradouroBean logradouroCadastrado = persistLogradouro("Rua");

      persistEndereco(logradouroCadastrado, "Mao Tse Tung", "Apto 12E");

   }

 

   public void testGetLazy() throws Exception{

      IEnderecoQueryFacade ieqf = getEnderecoQueryFacade();

      EnderecoBean enderecoCadastrado = ieqf.findEnderecoById(new Long(1));

      System.out.println(enderecoCadastrado.getTipoLogradouro().getTipo());

   }

 

   public IEnderecoQueryFacade getEnderecoQueryFacade(){

      try{

         InitialContext ctx = new InitialContext();

         Object ref = ctx.lookup("artigo-aptcore/EnderecoQueryFacade/remote");

         IEnderecoQueryFacade facade = IEnderecoQueryFacade.class.cast(ref);

         return facade;

      }catch(Exception e){

         e.printStackTrace();

      }

      return null;

   }

 

   public IEnderecoFacade getEnderecoFacade(){

      try{

         InitialContext ctx = new InitialContext();

         Object ref = ctx.lookup("artigo-apt-core/EnderecoFacade/remote");

         IEnderecoFacade facade = IEnderecoFacade.class.cast(ref);

         return facade;

      }catch(Exception e){

         e.printStackTrace();

      }

      return null;

   }

 

   public static void main(String argv[]) throws Exception{

      APTClient apt = new APTClient();

      apt.test();

      apt.testGetLazy();

   }

}

 

Para rodar o cliente, precisamos de alguns pacotes extras do container

para poder ter acesso ao servidor e nos comunicarmos com o componente

Endereço. Usaremos o ant para gerar o client.jar que contém todas as

interfaces e classes que precisamos para ter o cliente pronto pra rodar. O

build-client.xml também se encontra disponível para download.

 

Depois que o build-client.xml é rodado o diretório de pacotes fica assim:

 

PacoteComClient.jpg

 

Agora é só rodar a target run-client do build-client.xml para executar a

aplicaçäo, repare que a target faz referência a pacotes que säo do

container para ser executado.

 

Build-client.xml

 

.....

   

      

         

            

            

            

            

            

            

            

            

            

         

      

   

......

 

Quando rodamos a aplicaçäo, vimos que uma exceçäo é lancada pelo client

quando o método testGetLazy é executado. Mas por que isso acontece?

 

run-client:

[java] Sep 8, 2006 9:08:40 AM org.hibernate.LazyInitializationException

[java] SEVERE: could not initialize proxy - no Session

[java] org.hibernate.LazyInitializationException: could not initialize proxy -

no Session

[java] at

org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java

:57)

[java] at

org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializ

er.java:111)

[java] at

org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyIni

tializer.java:196)

[java] at

com.artigo.apt.persistence.TipoLogradouroBean_$$_javassist_0.getTipo(TipoLogradouro

Bean_$$_javassist_0.java)

[java] at com.artigo.apt.client.APTClient.testGetLazy(APTClient.java:49)

[java] at com.artigo.apt.client.APTClient.main(APTClient.java:79)

 

Quando executamos o método testGetLazy, recuperamos uma entidade

EnderecoBean leve, ou seja, sem a materializaçäo do Tipo de Logradouro, já

que o padräo do framework é que o carregamento seja Lazy. Porém a entidade

que recebemos do container é uma entidade detachada, ou seja, näo existe

vínculo nenhum com o container e consequentemente näo existe sessäo

transacional aberta, o que impossibilita o framework de fazer qualquer

consulta ao banco de dados a partir do cliente. Mas aí fica a duvída: quer

dizer que o Lazy só poderá ser usado se estivermos fazendo operaçöes dentro

do container, onde existe escopo transacional? A resposta é sim, a näo ser

que... :).

 

Solucäo:

 

O intuito deste artigo é fazer um Lazy Loading Remoto, utilizando os

Annotations e o APT para isso. Vamos analisar como será feito toda essa

mágica.

 

O que é Annotation?

 

Os annotations (JSR-175) säo um tipo especial de modificador e que podem

ser usados em qualquer lugar onde qualquer outro modificador possa ser usado.

Quando criamos um annotation, indicamos onde o mesmo se encaixa dentro da

estrutura do código Java. Por convençäo os annotations precedem outros modificadores,

e consistem de um sinal “@” seguido pelo tipo do annotation e uma lista de

pares chave-valor quando houver. Os annotations foram criados para “marcar” o

código, essas marcaçöes serviräo como fonte de informaçäo para um comportamento

diferente quando a marcaçäo colocada for lida.

Estaremos usando a palavra “marcar” para nos referirmos aos annotations em

certos pontos do artigo.

 

O que é APT?

 

O APT (Annotation Processing Tool) é uma ferramenta incorporada no Java SE

5.0 para processar os annotations inseridos nos códigos Java. Sua API

possui novas funcionalidades reflections para analisar o código criado a

procura dos annotations desejado. No nosso exemplo, iremos usar o APT para

verificar alguns annotations criados por nós para podermos gerar um Proxy.

Mais a frente diremos porque esse Proxy será importante.

O APT é acionado por linha de comando que executa um Processador de

Annotations que é instanciado e retornado através de uma Factory. Sua

principal finalidade é analisar códigos marcados com annotations e gerar

através das informaçöes fornecidas pelos mesmos arquivos de suporte à

aplicaçäo, como novos códigos fontes, classes, arquivos de configuraçäo

etc. O mecanismo do APT funciona da seguinte forma:

1. O APT verifica quais annotations estäo presente nos códigos fonte que

seräo processados.

2. O APT olha para a Factory de Processador de Annotations.

3. O APT pergunta para a Factory quais annotations seräo processados.

4. O APT solicita ao Factory a instância do Processador de Annotation

para processar os annotations existentes nos códigos fonte.

5. O Processador de Annotation é executado para processar o annotation.

  Se o processador gerar novos códigos fontes, o APT vai repetir este

processo até que nenhum código novo seja gerado.

 

Criando nossos annotations

 

Pensando friamente sobre o problema, vimos que o desejado é que no momento

que o método getTipoLogradouro for acionado, uma chamada a facade

EnderecoQueryFacade seja feita, e execute uma consulta no banco de dados

retornando o Tipo de Logradouro associado ao Endereco que estou

manipulando.

Agora vamos analisar que tipos de marcaçöes precisamos ter, para que tudo

funcione transparentemente.

 

1 – Precisamos de uma marcaçäo que nos diga qual classe precisa de um

proxy. Isso é importante para que o Processador de Annotations leia essa

marcaçäo e gere a nova classe Proxy.

 

Annotation Proxied.java

 

package com.artigo.apt.annotations;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Inherited;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* Annotation responsavel por marcar as classes que serao proxiadas

* Estando este annotation presente na classe, faz com que o Gerador de

* Proxy gere um proxy para a classe.

* @author rdias

*

*/

@Documented

@Retention(value=RetentionPolicy.RUNTIME)

@Target(value={ElementType.TYPE})

@Inherited

public @interface Proxied {

}

 

2 – Precisamos de uma marcaçäo que nos diga que o método é Lazy, e que

forneça informaçöes de como recuperar essa associaçäo.

 

Annotation ProxiedLazy.java

 

package com.artigo.apt.annotations;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Inherited;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(value={ElementType.METHOD})

@Inherited

public @interface ProxiedLazy {

   String mappedBy() default "";

   String jndiName() default "";

   String factoryClass() default "";

   String factoryMethod() default "";

   String[] parameters();

}

 

3 – Precisamos de uma marcaçäo que informe que a partir daquele ponto da

aplicaçäo o retorno do objeto deverá ser um objeto proxiado.

 

Annotation ProxiedReturn.java

 

package com.artigo.apt.annotations;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Inherited;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* Annotation responsavel por marcar os metodos cujo retorno devera

* ser convertido para um tipo Proxied.

*

* @author rdias

*

*/

@Documented

@Retention(value=RetentionPolicy.RUNTIME)

@Target(value={ElementType.METHOD})

@Inherited

public @interface ProxiedReturn {

}

 

4 – Precisamos de uma marcaçäo que indique que o método find em questäo é

um método que pode ser utilizado para recuperar informaçöes associativas.

 

Annotation ProxiedFind.java

 

package com.artigo.apt.annotations;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* Annotation responsavel por marcar os metodos que recuperam informacoes

associativas

* O key eh importante pois ele coloca unicidade em metodos de mesmo nome.

* @author rdias

*

*/

@Documented

@Retention(value=RetentionPolicy.RUNTIME)

@Target(value={ElementType.METHOD})

public @interface ProxiedFind {

   String key() default "";

}

 

5 – Precisamos de uma marcaçäo para ser colocada no proxy gerado, pois o

cliente poderá manipular o proxy achando que está trabalhando com o objeto

real “smart-reference”, e quando esse proxy for passado de volta pra o

container o mesmo deverá saber que se trata de um proxy e que uma açäo deve

ser tomada para que o objeto real seja recuperado do proxy.

 

Annotation ProxiedClass.java

 

package com.artigo.apt.annotations;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/***

* Annotation responsavel por marcar a classe proxiada

* Estando este annotation presente na classe proxiada, indicara

* ao engineer de injection que o que ele esta vendo na verdade

* eh um proxy de uma classe e nao a classe em si, toda a classe

* que possui este annotation devera ter um metodo getUnProxied() que

* retorna a classe real.

*

* @author rdias

*

*/

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public @interface ProxiedClass {

}

 

Atualizando as classes para que usem as marcaçöes

 

A primeira atualizaçäo que vamos fazer, será marcar a classe EnderecoBean

como sendo uma que deverá ter um proxy, e indicar que o método

getTipoLogradouro terá um comportamento Lazy Loading.

 

Atualizando a classe EnderecoBean.java

 

import com.artigo.apt.annotations.Proxied;

import com.artigo.apt.annotations.ProxiedLazy;

...

@Proxied

@Entity(name="EnderecoBean")

@Table(name="ENDERECO")

public class EnderecoBean implements java.io.Serializable{

...

   @ProxiedLazy(mappedBy="findLogradouroByEnderecoId",

               jndiName="artigo-apt-core/EnderecoQueryFacade/remote",

               parameters={"id"})

   @ManyToOne(optional=false)

   @JoinColumn(name="TIPO_LOGRADOURO_ID", referencedColumnName="ID")

   public TipoLogradouroBean getTipoLogradouro(){

         return tipoLogradouro;

   }

...

}

 

Agora vamos colocar na interface IEnderecoQueryFacade a marcaçäo que

indica que o retorno do método no caso o findEnderecoById, deverá na

verdade retornar um proxy do EnderecoBean e näo o próprio. Vamos também

inserir o método que irá retornar o tipo do logradouro pelo id do endereço.

 

Atualizando a interface IEnderecoQueryFacade.java

 

import com.artigo.apt.annotations.ProxiedReturn;

import com.artigo.apt.annotations.ProxiedFind;

...

@Remote

public interface IEnderecoQueryFacade {

   @ProxiedReturn

   public EnderecoBean findEnderecoById(Long id);

 

   @ProxiedFind(key=” findLogradouroByEnderecoId”)

   public TipoLogradouroBean findLogradouroByEnderecoId(Long id);

}

 

Com isso, já temos quatro marcaçöes nas nossas classes, das cinco que

precisamos para poder fechar a soluçäo. Mas até agora a única coisa que

fizemos foi colocar as marcaçöes; em momento algum criamos mecanismos que

iräo ler essas marcaçöes, entäo... vamos a isso.

Primeiramente vamos criar o Processador de Annotations que irá olhar

especificamente para o Annotation @Proxied.

 

Classe APTFactory.java

 

package com.artigo.apt.processor;

 

import java.util.ArrayList;

import java.util.Collection;

import java.util.Collections;

import java.util.Set;

import com.sun.mirror.apt.AnnotationProcessor;

import com.sun.mirror.apt.AnnotationProcessorEnvironment;

import com.sun.mirror.apt.AnnotationProcessorFactory;

import com.sun.mirror.apt.AnnotationProcessors;

import com.sun.mirror.declaration.AnnotationTypeDeclaration;

public class APTFactory implements AnnotationProcessorFactory

{

   /**

   * Retorna o processador para os annotations do artigo

   * @return Um annotation processor para o note annotations se requisitado,

   *  senao, retorna NO_OP annotation processor.

   *

   */

   public AnnotationProcessor getProcessorFor(

      Set declarations,

      AnnotationProcessorEnvironment env){

      AnnotationProcessor result;

      if (declarations.isEmpty()){

         result = AnnotationProcessors.NO_OP;

      }else{

         result = new APTAnnotationProcessor(env);

      }

      return result;

   }

 

   public Collection supportedAnnotationTypes(){

      Collection list = new ArrayList();

         list.add("com.artigo.apt.annotations.Proxied");

         return list;

   }

 

   public Collection supportedOptions(){

      return Collections.emptyList();

   }

}

 

A classe acima possui a responsabilidade de informar ao APT quais

annotations o processador vai analisar. Posterior a isso, o APT solicita à

factory a instância da classe processor, para ser acionada pelo APT através

do método process().

Criando a classe que irá processar o annotation.

 

Classe APTAnnotationProcessor.java

 

package com.artigo.apt.processor;

 

import java.util.ArrayList;

import java.util.Collection;

import java.util.List;

import com.sun.mirror.apt.AnnotationProcessor;

import com.sun.mirror.apt.AnnotationProcessorEnvironment;

import com.sun.mirror.declaration.AnnotationMirror;

import com.sun.mirror.declaration.AnnotationTypeDeclaration;

import com.sun.mirror.declaration.Declaration;

import com.sun.mirror.declaration.TypeDeclaration;

import com.sun.mirror.util.DeclarationVisitor;

import com.sun.mirror.util.DeclarationVisitors;

 

public class APTAnnotationProcessor implements AnnotationProcessor{

   private AnnotationProcessorEnvironment environment;

   private TypeDeclaration proxiedDeclaration;

   private Collection declarations = null;

   

   public APTAnnotationProcessor(AnnotationProcessorEnvironment env){

      environment = env;

      proxiedDeclaration =

         environment.getTypeDeclaration("com.artigo.apt.annotations.Proxied");

      declarations =

         environment.getDeclarationsAnnotatedWith((AnnotationTypeDeclaration)

proxiedDeclaration);

   }

 

   public void process(){

      for( Declaration declaration : declarations ){

         selectVisitor(declaration);

      }

   }

 

   private void selectVisitor(Declaration declaration){

      Collection annotations =

         declaration.getAnnotationMirrors();

      for(AnnotationMirror mirror : annotations){

         DeclarationVisitor scanner;

         if (mirror.getAnnotationType().getDeclaration().equals

                      (proxiedDeclaration)){

            scanner = DeclarationVisitors.getDeclarationScanner(

                        new ProxiedVisitor(environment),

                        DeclarationVisitors.NO_OP);

            declaration.accept(scanner);

         }

      }

   }

}

 

No momento que o método process() é acionado pelo APT, o mesmo percorre

todas as declaraçöes que possuem o annotation @Proxied. O objeto

Declaration armazena internamente a estrutura da classe que possui o

annotation. Quando passamos este objeto para o ProxiedVisitor, o APT se

responsabiliza por acionar o método visitor designado para ler a declaraçäo

específica que possui o annotation. No nosso caso, o @Proxied foi colocado

para marcar uma classe, entäo o APT vai acionar o método

visitClassDeclaration passando um objeto ClassDeclaration. Caso tivessemos

colocado nossa annotation na declaraçäo de um método, o APT iria acionar o

visitMethodDeclaration da nossa classe ProxiedVisitor e assim por diante.

Para maiores detalhes veja o Pattern Visitor.

 

Classe ProxiedVisitor.java

 

package com.artigo.apt.processor;

import com.sun.mirror.apt.AnnotationProcessorEnvironment;

import com.sun.mirror.declaration.ClassDeclaration;

import com.sun.mirror.util.SimpleDeclarationVisitor;

 

public class ProxiedVisitor extends SimpleDeclarationVisitor{

   private AnnotationProcessorEnvironment env = null;

   public ProxiedVisitor(AnnotationProcessorEnvironment env){

      this.env = env;

   }

   @Override

   public void visitClassDeclaration(ClassDeclaration cd) {

      GeneratorProxiedClass gpc = new GeneratorProxiedClass(cd, env);

      gpc.accept();

   }

}

Como já dissemos anteriormente, o fato de termos colocado o annotation

@Proxied na declaraçäo da classe EnderecoBean, faz com que o APT acione o

método visitClassDeclaration(..) passando a classe que foi anotada. Tendo

em mäos essa classe podemos começar a criar a nova classe proxy a partir

dessa. :)

 

Criando o gerador de código Proxy

 

Neste momento temos um objeto do tipo ClassDeclaration, que contém todas

as informaçöes pertinentes a classe que possui o annotation @Proxied. O que

temos que fazer agora, é analisar a classe e gerar um proxy dela mesma. Uma

informaçäo importante é que o APT tem poderes para criar qualquer arquivo

seja “.java” ou “.class” porém näo possui poderes para alterá-las. O que

quero dizer é que o ClassDeclaration por exemplo näo possui métodos do tipo

addNewMethod para inserir um novo método na classe que ele está analisando,

ou seja, toda classe analisada pelo APT é read-only. Mas o nosso intuito

näo é alterar a classe que possui o annotation @Proxied, o que queremos é

fazer um proxy dela. Bom.. poderíamos fazer algo bem simples, do tipo olhar

a classe e começar a criar a outra, mas vamos fazer diferente, vamos criar

um template do que será nossa nova classe proxy e gerar um parser para

poder moldá-la do jeito que quisermos. Isso se tornará útíl caso seja

necessário fazer uma atualizaçäo no proxy, como por exemplo, criar um cache

das associaçöes já retornadas, ou analisar um novo annotation contido no

objeto retornada. Enfim, a idéia do template é agilizar o processo de

atualizaçäo do novo código (Proxy), sem que aja a necessidade de abrir o

código do gerador para tal.

 

Arquivo Proxied.template (O template completo esta disponível para download)

 

...

 

@ProxiedClass

public class Proxied extends {

 

   ...

 

//Neste metodo vamos analisar a presença do annotation

//@ProxiedLazy e @ProxiedFind

   public Object getProxiedValue(String nameMethod){

      Class c = this.getClass().getSuperclass();

      Method[] methods = c.getMethods();

      for(Method m : methods ){

         if (m.getName().equals(nameMethod)){

            ProxiedLazy lazyRemote = m.getAnnotation(ProxiedLazy.class);

            if (lazyRemote != null){

               try{

                  String factoryClass = lazyRemote.factoryClass();

                  String jndiName = lazyRemote.jndiName();

                  String mappedBy = lazyRemote.mappedBy();

                  String[] parameters = lazyRemote.parameters();

                  Object objectWithLazyProxiedFind = null;

                  if (!factoryClass.equals("")){

                     Object factoryObject =

                        Class.forName(factoryClass, true,

                         Thread.currentThread().getContextClassLoader()).newInstance();

                     Method factoryMethod =

                        factoryObject.getClass().getMethod(

                           lazyRemote.factoryMethod(), new Class[]{});

                     objectWithLazyProxiedFind = factoryMethod.invoke

                              (factoryObject, new Object[]{});

                  }else if(!jndiName.equals("")){

                     try {

                        Context ct = new InitialContext();

                        objectWithLazyProxiedFind = ct.lookup(jndiName);

                     }catch (Exception e){

                        e.printStackTrace();

                     }

                  }

                  Method objectWithLazyFindMethod = null;

                  Class[] arrClazz = objectWithLazyProxiedFind.getClass().getInterfaces();

                  for(Class clazz : arrClazz ){

                     Method[] methodsClazz = clazz.getMethods();

                     for(Method methodClazz : methodsClazz){

                        ProxiedFind ProxiedFind = methodClazz.getAnnotation

                                 (ProxiedFind.class);

                        if (ProxiedFind != null && ProxiedFind.key().equals(mappedBy)){

                           objectWithLazyFindMethod =

                               objectWithLazyProxiedFind.getClass().getMethod(

                              methodClazz.getName(), methodClazz.getParameterTypes() );

                           break;

                        }

                     }

                     if (objectWithLazyFindMethod != null)

                        break;

                     }

                     System.out.println("Executando o metodo proxiado:

                      " + objectWithLazyFindMethod.getName() +

                        " com " + (parameters.length + 1) + " parametros.");

                     return objectWithLazyFindMethod.invoke

                      (objectWithLazyProxiedFind, toObject(parameters) );

               }catch(Exception ee){

                  ee.printStackTrace();

               }

            }

         }

       }

       return null;

   }

 

   ...

}

 

 

Vamos criar uma classe abstrata SimpleTemplateVisitor.java que irá percorrer

o template linha a linha atrás das diretivas que incluimos no template.

Sempre que ele achar uma diretiva irá direcionar a linha do arquivo para um

determinado método que trata aquela diretiva em questäo. Recorreremos ao

Pattern Visitor já que o APT o utiliza por padräo.

 

Classe SimpleTemplateVisitor.java

(O código completo está disponível para download)

 

...

 

public abstract class SimpleTemplateVisitor {

   

   AnnotationProcessorEnvironment env;

   Map mapMethods = new HashMap(){{

      put("package.qualifiedName", "visitorPackage");

      put("class.simpleName", "visitorNameClass");

      put("class.type", "visitorTypeClass");

      put("class.simpleName.field", "visitorField");

      put("class.methods", "visitorMethods");

   }};

 

   public SimpleTemplateVisitor(AnnotationProcessorEnvironment env){

      this.env = env;

   }

 

   ...

 

   public abstract String visitorPackage(String line);

   public abstract String visitorNameClass(String line);

   public abstract String visitorTypeClass(String line);

   public abstract String visitorField(String line);

   public abstract String visitorMethods(String line);

   public abstract URL getTemplate();

   public void endTemplate(){

   }

 

   ...

}

 

Agora vamos criar a classe que irá utilizar o template para poder gerar o

nosso código Proxy. Repare no template, que o nome da nova classe será o

mesmo nome da classe marcada com @Proxied seguido de Proxied:

 

Classe GeneratorProxiedClass.java

(O código completo está disponível para download)

 

...

 

public class GeneratorProxiedClass extends SimpleTemplateVisitor {

   final String PATH_TEMPLATE =

      "/home/rdias/projects/TigerSistafe/Artigos/resources/";

... 

 

   public void endTemplate(){

      System.out.println(".........Gerando Codigo....................");

      System.out.println(newPackage + ".proxied." + newClass+"Proxied");

      try{

         PrintWriter writer = env.getFiler().createSourceFile(newPackage +

            ".proxied." + newClass+"Proxied");

         writer.append(newSource.toString());

         System.out.println(".........Codigo Gerado.................");

      }catch(Exception e){

         e.printStackTrace();

      }

   }

 

   public String visitorPackage(String line){

      String packageName = classDeclaration.getPackage().getQualifiedName();

      this.newPackage = packageName;

      return replace(line, "", packageName);

   }

 

   public String visitorNameClass(String line){

      String nameClass = classDeclaration.getSimpleName();

      this.newClass = nameClass;

      return replace(line, "", nameClass);

   }

 

   public String visitorTypeClass(String line){

      return "";

   }

 

   public String visitorField(String line){

      String nameField = "my"+classDeclaration.getSimpleName();

      return replace(line, "", nameField);

   }

 

   public String visitorMethods(String line){

      StringBuffer sourceString = new StringBuffer("");

      Collection methods = classDeclaration.getMethods();

      System.out.println("Tamanho de methods:" + methods.size() );

      setHierarquicalMethods( classDeclaration, methods );

      System.out.println("Tamanho de methods:" + methods.size() );

      for(MethodDeclaration m : methods){

         if (!isMethodAbstractOrPrivate(m)){

            //verifica os anotations presente no metodo

            Collection annotations =

               m.getAnnotationMirrors();

            boolean processLazy = false;

            for(AnnotationMirror mirror : annotations){

               if(mirror.getAnnotationType().getDeclaration()

                     .getSimpleName().equals("ProxiedLazy")){

                  sourceString.append(

                     generatorMethodBody(m, mirror, line));

                  processLazy = true;

               }

            }

            if (!processLazy){

               sourceString.append(generatorMethodBody(m));

            }

         }

      }

      return sourceString.toString();

   }

 

 

   ...

}

 

A classe acima simplesmente recupera as diretivas e altera as mesmas por

nomes retirados da classe original.

Vamos executar o APT pra ver se ele gera nossa classe Proxy.

Para rodar via linha de comando, vá para o diretório onde estäo suas

entidades. Isso é necessário para que näo seja preciso colocar o caminho

completo de todas as entidades marcadas com o annotation @Proxied lá

embaixo onde está o “*.java ”:

->

/home/rdias/projects/TigerSistafe/Atigos/scr/com/artigo/apt/persistence

-> Digite:

-> apt -cp /home/rdias/projects/TigerSistafe/Artigos/build/:

/usr/jboss-4.0.4.GA/server/default/lib/ejb3-persistence.jar/

-s /home/rdias/projects/TigerSistafe/Artigos/src -nocompile

-factory com.artigo.apt.processor.APTFactory *.java

A opcäo -s informa onde estäo os fontes

A opcäo -cp informa onde estäo as classes

A opcäo -nocompile näo compila as novas classes geradas.

A opcäo -factory diz quem é a factory que irá processar os Annotations.

Outra informacäo importante é que a API com.sun.mirror* utilizada pelas

nossas classes para criar o Processador, se encontra dentro do pacote

tools.jar no diretório jdk1.5.0_05/lib/tools.jar.

 

ConsoleGerador.jpg

 

Uma observaçäo interessante, é que depois que a classe EnderecoBeanProxied

foi gerada o APT verificou que nela existe o annotation @ProxiedClass que

näo está sendo processado por ninguém, o que reforça o passo 5 do ciclo do

APT. Veja que foi criado no seu diretório source um novo pacote:

com.artigo.apt.persistence.proxied

e nele está contido a classe EnderecoBeanProxied.java que possui a mesma

estrutura definida no seu template. :).

No build existe uma target chamada aptProxied que executa o APT via ant,

sem a necessidade de abrir o console e fazer isso por linha de comando. O

incoveniente é que neste caso toda classe que possui o annotation @Proxied

deverá ser colocada como argumento para execuçäo do APT.

 

Build.xml

...

   

   

      

         

         

         

         

         

      

   

...

 

Revisando um pouco, afinal já se foram muitas linhas de código.

Inicialmente criamos 5 annotations, depois colocamos esses annotations em

algumas classes. Agora estamos analisando essas marcaçöes e gerando

resultados em cima delas. Por enquanto só analisamos os annotation @Proxied

pelo gerador. O @ProxiedLazy e @ProxiedFind estäo sendo analisados pela

classe proxy gerada, faltando agora o @ProxiedReturn e @ProxiedClass. O

annotation @ProxiedReturn deve ser verificado quando o usuário solicitar ao

componente EnderecoQueryFacade o endereço pelo Id, para isso vamos fazer o

seguinte, criaremos um proxy dinâmico que encapsula o componente

EnderecoQueryFacade, e retorna pro cliente esse proxy dinâmico. No momento

que o cliente acessar o método findEnderecoById, nosso proxy dinâmico irá

analisar se o método acionado pelo cliente tem o annotation @ProxiedReturn.

Caso sim, o proxy dinâmico gera uma instância do proxy gerado por nós,

passando como argumento o EnderecoBean retornando o proxy para o cliente,

calma que olhando o código fica mais simples:

 

Classe ProxiedInvocationHandler.java

 

package com.artigo.apt.processor;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.util.Collection;

import java.util.Iterator;

import java.util.Map;

import com.artigo.apt.annotations.ProxiedReturn;

 

public class ProxiedInvocationHandler implements InvocationHandler{

   

   private Object delegate;

   private Class delegateClass;

 

   public ProxiedInvocationHandler(Object delegate, Class interfaceClass)

   {

      this.delegate = delegate;

      this.delegateClass = delegate.getClass();

   }

 

   public Object invoke(Object proxy, Method method, Object[] args)

      throws Exception{

      try {

         Method methodForInvoke =

            delegateClass.getMethod(method.getName(), method

               .getParameterTypes());

         Object result = methodForInvoke.invoke(delegate, args);

         if (method.isAnnotationPresent(ProxiedReturn.class)){

            if (result instanceof Collection){

               proxiedCollection( (Collection)result );

            }else if (result instanceof Map){

               //nao implementado ainda

            }else{

               return proxied(result);

            }

         }

         return result;

      }catch (Throwable e){

         e.printStackTrace();

         throw new Exception("Erro:" + e.getMessage() + " Causado por: " +

         e.getCause());

      }

   }

 

   private Object proxied(Object result) throws Exception{

      Class resultClass = result.getClass();

      String packageName = resultClass.getPackage().getName()+".proxied.";

      String nameClassProxied = packageName +

         resultClass.getSimpleName()+"Proxied";

      Class c = Class.forName(nameClassProxied, false,

                  Thread.currentThread().getContextClassLoader());

      return c.getConstructor(new Class[]{resultClass}).newInstance(result);

   }

 

   private void proxiedCollection(Collection resultCollection) throws Exception{

      for(Iterator it = resultCollection.iterator(); it.hasNext();){

         Object o = it.next();

         resultCollection.remove( o );

         resultCollection.add( proxied(o) );

      }

   }

 

   private void proxiedMap(Map resultMap){

      //TODO: Nao implementar por enquanto

   }

}

 

Vamos agora criar uma EnderecoFacadeFactory que faz uso do

InvocationHandler criado acima. Essa Facade será responsavél por retornar

os objetos EnderecoFacade e EnderecoQueryFacade para o cliente.

 

Classe EnderecoFacadeFactory.java

 

package com.artigo.apt;

 

import java.lang.reflect.Proxy;

import javax.naming.Context;

import javax.naming.InitialContext;

import com.artigo.apt.interfaces.IEnderecoFacade;

import com.artigo.apt.interfaces.IEnderecoQueryFacade;

import com.artigo.apt.processor.ProxiedInvocationHandler;

 

public class EnderecoFacadeFactory{

 

   static final String ENDERECO_FACADE_JNDI = "EnderecoFacade";

   static final String ENDERECO_QUERY_FACADE_JNDI = "EnderecoQueryFacade";

   static final String ENDERECO_COMPONENT = "artigo-apt-core";

   

   public static IEnderecoFacade getEnderecoFacade() throws Exception{

      return (IEnderecoFacade)getProxiedFacade(

            ENDERECO_COMPONENT,

            ENDERECO_FACADE_JNDI,

            "remote",

            IEnderecoFacade.class);

   }

 

   public static IEnderecoQueryFacade getEnderecoQueryFacade() throws Exception{

      return (IEnderecoQueryFacade)getProxiedFacade(

            ENDERECO_COMPONENT,

            ENDERECO_QUERY_FACADE_JNDI,

            "remote",

            IEnderecoQueryFacade.class);

   }

   

   private static Object getProxiedFacade(

      String jndiContext,String jndiName,

      String typeAccess,Class interfaceClass) throws Exception{

      

      try{

         Context ct = new InitialContext();

         Object ref =

            ct.lookup(jndiContext+"/"+jndiName+"/"+typeAccess);

         return Proxy.newProxyInstance(

            Thread.currentThread().getContextClassLoader(),

            new Class[]{interfaceClass},

            new ProxiedInvocationHandler(interfaceClass.cast(ref),

                                         interfaceClass));

      }catch (Exception e){

         throw e;

      }

   }

}

 

   Esta classe deverá estar disponível para o cliente, pois será através dela

que o cliente irá ter acesso aos componentes EnderecoFacade (para açöes de

persistência) e EnderecoQueryFacade (para acöes de consulta).

 

Alterar a classe APTClient.java:

 

....

   public IEnderecoQueryFacade getEnderecoQueryFacade(){

      try{

         return EnderecoFacadeFactory.getEnderecoQueryFacade();

      }catch(Exception e){

         e.printStackTrace();

      }

      return null;

   }

 

   public IEnderecoFacade getEnderecoFacade(){

      try{

         return EnderecoFacadeFactory.getEnderecoFacade();

      }catch(Exception e){

         e.printStackTrace(); 

      }

      return null;

   }

...

 

Agora é só fazer o build-deploy do componente endereço e rodar o buildclient

para que o client.jar sejá gerado novamente, rode a target runclient

para executar:

 

Buildfile: /home/rdias/projects/TigerSistafe/Artigos/build-client.xml

run-client:

[java] Executando o metodo proxiado:findLogradouroByEnderecoId com 1

parametro(s).

[java] Rua

BUILD SUCCESSFUL

Total time: 1 second

 

Pronto!

Agora o acesso ao tipo logradouro foi feito via nosso proxy. Caso näo

deseje que o proxy gerado seja usado, simplesmente retire o annotation

@ProxiedReturn e faça redeploy da aplicaçäo, e geraçäo do client.jar. No

momento que o cliente for executado o erro original irá voltar a acontecer,

pelos mesmo motivos já explicado no inicio do artigo.

 

Conclusäo

 

Lazy Loading é um recurso poderoso na obtenção de relacionamentos entre

entidades, pois nos dá a oportunidade de trabalharmos com entidades leves

tornando as consultas mais rapidas e enxutas. Essas caracteristicas

justificam sua adoção como padrão para os fetches no JPA, porém o fato de só

termos esse recurso enquanto tivermos no container, deixa uma limitação quanto

ao seu uso. Neste artigo resolvemos parte desse problema com o uso do APT e

dos annotations.

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?