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