terça-feira, 20 de outubro de 2009

VRaptor 3 - Poupando recursos - LAZY Dependence Injection

Objetivo: Ao final deste artigo espera-se que você saiba como poupar recursos caros, trazendo eles de forma LAZY, ou como prefiro chamar Just-in-Time (no momento certo).

No VRaptor3 a injeção de dependencia ficou bem mais fácil, os interceptadores que eram os responsáveis para injetar a dependencia sumiram e agora fica tudo a cargo do container, que pode ser o Spring ou o Pico.

A facilidade na injeção de dependencia tem um custo, como não é mais controlado pelo programador que cria o interceptor sempre que declaramos uma dependencia no construtor de um @Component, @Resource ou @Intercepts ele é injetado no inicio, logo na construção, porem as vezes o fluxo de um requisição faz com que não usemos algumas destas injeções de dependencia, disperdiçando recursos valiosos.

Por exemplo, vamos supor o seguinte @Resource abaixo, que cadastra produtos

  1. import java.util.List;
  2.  
  3. import org.hibernate.Session;
  4.  
  5. import br.com.caelum.vraptor.Result;
  6. import br.com.caelum.vraptor.view.Results;
  7.  
  8. public class ProdutoController {
  9.         /**
  10.          * O recurso que queremos poupar.
  11.          */
  12.         private final Session session;
  13.         private final Result result;
  14.        
  15.         public ProdutoController(final Session session, final Result result) {
  16.                 this.session = session;
  17.                 this.result = result;
  18.         }
  19.        
  20.         /**
  21.          * apenas renderiza o formulário
  22.          */
  23.         public void form() {}
  24.        
  25.         public List<Produto> listar() {
  26.                 return session.createCriteria(Produto.class).list();
  27.         }
  28.        
  29.         public Produto adiciona(Produto produto) {
  30.                 session.persist(produto);
  31.                 result.use(Results.logic()).redirectTo(getClass()).listar();
  32.                 return produto;
  33.         }
  34. }


Sempre que alguem faz uma requisição a qualquer lógica dentro do recurso ProdutoController uma Session é aberta, porem note que abrir o formulário para adicionar produtos não requer sessão com o banco, ele apenas renderiza uma página, cada vez que o formulário de produtos é aberto um importante e caro recurso do sistema esta sendo requerido, e de forma totalmente ociosa.

Como agir neste caso ? isolar o formulário poderia resolver este problema mais recairia em outro, da mantenabilidade.

O ideal é que este recurso só fosse realmente injetado no tempo certo (Just in Time) como seria possivel fazer isso ? a solução é usar proxy dinamicos, enviando uma session que só realmente abrirá a conexão com o banco quando um de seus métodos for invocado

  1. import java.lang.reflect.Method;
  2.  
  3. import javax.annotation.PreDestroy;
  4. import org.hibernate.classic.Session;
  5. import org.hibernate.SessionFactory;
  6.  
  7. import net.vidageek.mirror.dsl.Mirror;
  8.  
  9. import br.com.caelum.vraptor.ioc.ApplicationScoped;
  10. import br.com.caelum.vraptor.ioc.Component;
  11. import br.com.caelum.vraptor.ioc.ComponentFactory;
  12. import br.com.caelum.vraptor.ioc.RequestScoped;
  13. import br.com.caelum.vraptor.proxy.MethodInvocation;
  14. import br.com.caelum.vraptor.proxy.Proxifier;
  15. import br.com.caelum.vraptor.proxy.SuperMethod;
  16.  
  17. /**
  18.  * <b>JIT (Just-in-Time) {@link Session} Creator</b> fábrica para o componente {@link Session}
  19.  * gerado de forma LAZY ou JIT(Just-in-Time) a partir de uma {@link SessionFactory}, que
  20.  * normalmente se encontra em um ecopo de aplicativo @{@link ApplicationScoped}.
  21.  *
  22.  * @author Tomaz Lavieri
  23.  * @since 1.0
  24.  */
  25. @RequestScoped
  26. public class JITSessionCreator implements ComponentFactory<Session> {
  27.        
  28.     private static final Method CLOSE = new Mirror().on(Session.class).reflect().method("close").withoutArgs();
  29.     private static final Method FINALIZE = new Mirror().on(Object.class).reflect().method("finalize").withoutArgs();
  30.     private final SessionFactory factory;
  31.     /** Guarda a Proxy Session */
  32.         private final Session proxy;
  33.         /** Guarada a Session real. */
  34.         private Session session;
  35.        
  36.         public JITSessionCreator(final SessionFactory factory, final Proxifier proxifier) {
  37.                 this.factory = factory;
  38.                 this.proxy = proxify(Session.class, proxifier); // *1*
  39.         }
  40.        
  41.         /**
  42.          * Cria o JIT Session, que repassa a invocação de qualquer método, exceto
  43.          * {@link Object#finalize()} e {@link Session#close()}, para uma session real, criando
  44.          * uma se necessário.
  45.          */
  46.         private Session proxify(Class<? extends Session> target, Proxifier proxifier) {
  47.                 return proxifier.proxify(target, new MethodInvocation<Session>() {
  48.                     @Override // *2*
  49.                     public Object intercept(Session proxy, Method method, Object[] args, SuperMethod superMethod) {
  50.                         if (method.equals(CLOSE) || (method.equals(FINALIZE) && session == null)) {
  51.                             return null; //skip
  52.                         }
  53.                         return new Mirror().on(getSession()).invoke().method(method).withArgs(args);
  54.                     }
  55.                 });
  56.         }
  57.        
  58.         public Session getSession() {
  59.                 if (session == null) // *3*
  60.                         session =       factory.openSession();
  61.                 return session;
  62.         }
  63.        
  64.         @Override
  65.         public Session getInstance() {
  66.                 return proxy; // *4*
  67.         }
  68.        
  69.         @PreDestroy
  70.         public void destroy() { // *5*
  71.                 if (session != null && session.isOpen()) {
  72.                         session.close();
  73.                 }
  74.         }
  75. }


Explicando alguns pontos chaves, comentados com // *N*
  1. O Proxfier é um objeto das libs do vrapor que auxilia na criação de objetos proxys ele é responsável por escolher a biblioteca que implementa o proxy dinamico, e então invocar via callback um método interceptor, como falamo abaixo.

  2. Neste ponto temos a implementação do nosso interceptor, sempre que um método for envocado em nosso proxy, esse intereptor é invocado primeiro, ele filtra as chamada ao método finalize caso a session real ainda não tenha sido criada, isso evita criar a session apenas para finaliza-la.
    O método close também é filtrao, isso é feito para evitar criar uma session apenas para fecha-la, e também por que o nosso SessionCreator é que é o responsavel por fechar a session ao final do scopo, quando a request acabar.
    Todos os outros métodos são repassados para uma session através do método getSession() onde é realmente que acontece o LAZY ou JIT.

  3. Aqui é onde acontece a mágia, da primeira vez que getSession() é invocado a sessão é criada, e então repassada, todas as outras chamadas a getSession() repassam a sessão anteriormente criada, assim, se getSession() nunca for envocado, ou seja, se nenhum método for envocado no proxy, getSession() nunca será invocado, e a sessão real não será criada.

  4. O retorno desse ComponentFactory é a Session proxy, que só criará a session real se um de seus métodos for invocado.

  5. Ao final do escopo o destroy é invocado, ele verifica se a session real existe, existindo verifica se esta ainda esta aberta, e estando ele fecha, desta forma é possivel garantir que o recurso será sempre liberado.


Assim podemos agora pedir uma session sempre que acharmos que vamos precisar de uma, sabendo que o recurso só será realmente solicitado quando formos usar um de seus métodos, salvando assim o recurso.

Esta mesma abordagem pode ser usada para outros recursos caros do sistema.

Os códigos fonte para os ComponentFactory de EntityManager e Session que utilizo podem ser encontrados neste link: http://guj.com.br/posts/list/141500.java

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}

Seguidores