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

Nenhum comentário:

Postar um comentário

Seguidores