sábado, 17 de outubro de 2009

VRaptor3 - Interceptando recursos anotados

Em primeira lugar, gostaria de tecer meus mais sinceros elogios a equipe VRaptor, a versão 3 esta muito boa, bem mais intuitiva e fácil de usar que a 2.6

Neste artigo vou mostar como interceptar um método de um Resource especifico, identificando-o a partir de uma anotação e executar ações antes do método executar, e após ele executar.

Vamos supor que nós temos o seguinte Resource para adicionar produtos no nosso sistema


  1. @Resource
  2. public class ProdutoController {
  3.         private final DaoFactory factory;
  4.         private final ProdutoDao dao;
  5.        
  6.         public ProdutoController(DaoFactory factory) {
  7.                 this.factory = factory;
  8.                 this.dao = factory.getProdutoDao();
  9.         }
  10.        
  11.         public List<Produto> listar() {
  12.                 return dao.list();
  13.         }
  14.        
  15.         public Produto atualizar(Produto produto) {
  16.                 try {
  17.                         factory.beginTransaction();
  18.                         produto = dao.update(produto);
  19.                         factory.commit();
  20.                         return produto;
  21.                 } catch (DaoException ex) {
  22.                         factory.rollback();
  23.                         throw ex;
  24.                 }
  25.         }
  26.        
  27.         public Produto adicionar(Produto produto) {
  28.                 try {
  29.                         factory.beginTransaction();
  30.                         produto = dao.store(produto);
  31.                         factory.commit();
  32.                         return produto;
  33.                 } catch (DaoException ex) {
  34.                         factory.rollback();
  35.                         throw ex;
  36.                 }
  37.         }
  38. }


Agora nos queremos que um interceptador intercepte meu recurso, e execute a lógica dentro de um escopo transacional, como fazer isso ? é só criar um interceptador assim.

  1. import org.hibernate.Session;
  2. import org.hibernate.Transaction;
  3.  
  4. import br.com.caelum.vraptor.Intercepts;
  5. import br.com.caelum.vraptor.core.InterceptorStack;
  6. import br.com.caelum.vraptor.interceptor.Interceptor;
  7. import br.com.caelum.vraptor.resource.ResourceMethod;
  8.  
  9. @Intercepts
  10. public class TransactionInterceptor implements Interceptor {
  11.         private final Session session;
  12.         public TransactionInterceptor(Session session) {
  13.                 this.session = session;
  14.         }
  15.         public void intercept(InterceptorStack stack, ResourceMethod method, Object instance) {
  16.                 Transaction transaction = null;
  17.                 try {
  18.                         transaction = session.beginTransaction();
  19.                         stack.next(method, instance);
  20.                         transaction.commit();
  21.                 } finally {
  22.                         if (transaction != null && transaction.isActive()) {
  23.                                 transaction.rollback();
  24.                         }
  25.                 }
  26.         }
  27.         public boolean accepts(ResourceMethod method) {
  28.                 return true; //aceita todas as requisições
  29.         }
  30. }


Ok, o interceptador vai rodar e abrir transação antes e depois de executar a logica, e os métodos transacionais da minha lógica irão se reduzir a isto

  1. public Produto atualizar(Produto produto) {
  2.         return dao.update(produto);
  3. }
  4.  
  5. public Produto adicionar(Produto produto) {
  6.         return dao.store(produto);
  7. }


Ok mas, neste caso temos o problema de que métodos que não exigem transação estão abrindo e fechando transação a cada requisição sem necessidade.

Como então selecionar apenas algumas lógicas para serem transacionais ? podem criar uma anotação para isto, desta forma:

  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5.  
  6. /**
  7.  * Usado para garantir que um determinado recurso interceptado seja executada em um
  8.  * escopo de tranzação.
  9.  * @author Tomaz Lavieri
  10.  * @since 1.0
  11.  */
  12. @Retention(RetentionPolicy.RUNTIME)
  13. @Target({ElementType.METHOD,ElementType.TYPE})
  14. public @interface Transactional {}


Agora precisamos marcar os pontos onde queremos que o escopo seja transacional com esta anotação.

  1. @Resource
  2. public class ProdutoController {
  3.         private final ProdutoDao dao;
  4.        
  5.         public ProdutoController(ProdutoDao dao) {
  6.                 this.dao = dao;
  7.         }
  8.        
  9.         public List<Produto> listar() {
  10.                 return dao.list();
  11.         }
  12.        
  13.         @Transactional
  14.         public Produto atualizar(Produto produto) {
  15.                 return dao.update(produto);
  16.         }
  17.        
  18.         @Transactional
  19.         public Produto adicionar(Produto produto) {
  20.                 return dao.store(produto);
  21.         }
  22. }


Ok o código ficou bem mais enxuto, mas como interceptar apenas os métodos marcados com esta anotação ?? para tal basta no nosso accepts do TransactionInterceptor verificarmos se a anotação esta presente no método, ou no proprio rescurso (quando marcado no recurso todos os métodos do recurso seriam transacionais).

A modificação do método ficaria assim

  1.         public boolean accepts(ResourceMethod method) {
  2.                 return  method
  3.                                 .getMethod() //metodo anotado
  4.                                 .isAnnotationPresent(Transactional.class)
  5.                         || method
  6.                                 .getResource() //ou recurso anotado
  7.                                 .getType()
  8.                                 .isAnnotationPresent(Transactional.class);
  9.         }


Pronto agora somente os métodos com a anotação @Transacional são executados em escopo de transação e economisamos linhas e linhas de códigos de try{commit}catch{rollback throw ex}

Um comentário:

  1. Parabens Tomaz! Vamos linkar no site assim que tiver um novo deploy!

    ResponderExcluir

Seguidores