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
-
import java.util.List;
-
-
import org.hibernate.Session;
-
-
import br.com.caelum.vraptor.Result;
-
import br.com.caelum.vraptor.view.Results;
-
-
public class ProdutoController {
-
/**
-
* O recurso que queremos poupar.
-
*/
-
private final Session session;
-
private final Result result;
-
-
public ProdutoController(final Session session, final Result result) {
-
this.session = session;
-
this.result = result;
-
}
-
-
/**
-
* apenas renderiza o formulário
-
*/
-
public void form() {}
-
-
public List<Produto> listar() {
-
return session.createCriteria(Produto.class).list();
-
}
-
-
public Produto adiciona(Produto produto) {
-
session.persist(produto);
-
result.use(Results.logic()).redirectTo(getClass()).listar();
-
return produto;
-
}
-
}
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
-
import java.lang.reflect.Method;
-
-
import javax.annotation.PreDestroy;
-
import org.hibernate.classic.Session;
-
import org.hibernate.SessionFactory;
-
-
import net.vidageek.mirror.dsl.Mirror;
-
-
import br.com.caelum.vraptor.ioc.ApplicationScoped;
-
import br.com.caelum.vraptor.ioc.Component;
-
import br.com.caelum.vraptor.ioc.ComponentFactory;
-
import br.com.caelum.vraptor.ioc.RequestScoped;
-
import br.com.caelum.vraptor.proxy.MethodInvocation;
-
import br.com.caelum.vraptor.proxy.Proxifier;
-
import br.com.caelum.vraptor.proxy.SuperMethod;
-
-
/**
-
* <b>JIT (Just-in-Time) {@link Session} Creator</b> fábrica para o componente {@link Session}
-
* gerado de forma LAZY ou JIT(Just-in-Time) a partir de uma {@link SessionFactory}, que
-
* normalmente se encontra em um ecopo de aplicativo @{@link ApplicationScoped}.
-
*
-
* @author Tomaz Lavieri
-
* @since 1.0
-
*/
-
@RequestScoped
-
public class JITSessionCreator implements ComponentFactory<Session> {
-
-
private static final Method CLOSE = new Mirror().on(Session.class).reflect().method(“close”).withoutArgs();
-
private final SessionFactory factory;
-
/** Guarda a Proxy Session */
-
private final Session proxy;
-
/** Guarada a Session real. */
-
private Session session;
-
-
public JITSessionCreator(final SessionFactory factory, final Proxifier proxifier) {
-
this.factory = factory;
-
this.proxy = proxify(Session.class, proxifier); // *1*
-
}
-
-
/**
-
* Cria o JIT Session, que repassa a invocação de qualquer método, exceto
-
* {@link Object#finalize()} e {@link Session#close()}, para uma session real, criando
-
* uma se necessário.
-
*/
-
private Session proxify(Class<? extends Session> target, Proxifier proxifier) {
-
return proxifier.proxify(target, new MethodInvocation<Session>() {
-
@Override // *2*
-
if (method.equals(CLOSE) || (method.equals(FINALIZE) && session == null)) {
-
return null; //skip
-
}
-
return new Mirror().on(getSession()).invoke().method(method).withArgs(args);
-
}
-
});
-
}
-
-
public Session getSession() {
-
if (session == null) // *3*
-
session = factory.openSession();
-
return session;
-
}
-
-
@Override
-
public Session getInstance() {
-
return proxy; // *4*
-
}
-
-
@PreDestroy
-
public void destroy() { // *5*
-
if (session != null && session.isOpen()) {
-
session.close();
-
}
-
}
-
}
Explicando alguns pontos chaves, comentados com // *N*
- 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.
- 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. - 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.
- O retorno desse ComponentFactory é a Session proxy, que só criará a session real se um de seus métodos for invocado.
- 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