VRpator3 Problema com Injeção de Dependencia antes do accept()

12 respostas
Lavieri

Bom pessoal, encontrei um problemaão de performance que gostaria de ver com vcs do VRaptor como resolver...

O problema é que a Injeção de dependencia nos interceptors são feitas antes do método accept ser rodado.

E como o interceptor é criado sempre para toda requisição, mesmo quando você não aceita interceptar um método, os recursos já foram injetados e vc perde muita performance....

Exemplificando melhor, estou montando meu TransactionInterceptor, e o problema é que ele esta abrindo uma session e injetando no meu interceptor mesmo quando retorno false no método accept.

E desta forma paginas que deveriam só renderizar dados, e não fazer ligações ao banco de dados, estão atoa puxando uma sessão do hibernate...

Eu tenho anotações @Transaction que coloco nos meus @Resource quando quero rodalos sobre uma tranzação, e as sessões só deveriam ser injetadas nestas condições... =/ ....

quando abro uma logica que não tem conexão com o banco, que não pede nenhum DAO, por conta do interceptor uma sessão com Hibernate esta sendo aberta desnecessáriamente =/

como resolvo isso ??

uma solução seria testar o accept em um método estatico, o que dificultaria, pois não daria para implementar uma interface assim =/

um exemplo de recurso que não deveria abrir sessão com hibernate
@Resource
public class IndexController {
	
	@Path("/")
	public void index() { 
		//como o interceptor roda antes deste método ser executado
		//uma Session acaba sendo aberta atoa pelo Interceptor =/
	}
}

um exemplo de recurso que deveria abrir sessão com o banco

@Resource
public class PessoaController {
	private final PessoaDao pessoaDao;
	
	public PessoaController(PessoaDao pessoaDao) {
		this.pessoaDao = pessoaDao;
	}

	@Path("/pessoas")
	@Get
	public List<Pessoa> list() { //tranzação não é necessária
		return pessoaDao.list();
	}

	@Path("/pessoas")
	@Post
	@Transaction
	public Pessoa adicionar(Pessoa pessoa) { //aqui  um interceptor de tranzação
		daoPessoa.persist(pessoa);
		return pessoa;
	}
	
	@Path("/pessoas/{pessoa.id}")
	@Post
	@Transaction
	public Pessoa atualizar(Pessoa pessoa) { //arqui  um interceptor de tranzação
		pessoaDao.marge(pessoa);
		return pessoa;
	}
}

meu Interceptor...

@Intercepts
@RequestScoped
public class TransactionInterceptor implements Interceptor {
	private final Session session;

	public TransactionInterceptor(Session session) {
		this.session = session;
	}

	public void intercept(InterceptorStack stack, ResourceMethod method, Object instance) {
		org.hibernate.Transaction transaction = null;
		try {
			transaction = session.beginTransaction();
			stack.next(method, instance);
			transaction.commit();
		} finally {
			if (transaction != null && transaction.isActive()) {
				transaction.rollback();
			}
		}
	}

	public boolean accepts(ResourceMethod method) {
		return method
			.getMethod()
			.isAnnotationPresent(Transaction.class);
	}
}

12 Respostas

sergiolopes

Talvez fazer duas interfaces separadas: uma com o accept e outra com o intercepts (tipo InterceptorAcceptor e InterceptorExecutor).
Aí voce poderia implementar 2 classes separadas ou qdo nao houver necessidade/perda de performance, faria a mesma classe implementar as duas interfaces.
E pra nao quebrar compatibilidade, de repente ate fazer a interface Interceptor extends InterceptorAcceptor e InterceptorExecutor…

Bom, jogando ideias… o resto do povo precisa aprovar isso =)

Paulo_Silveira

a ideia do Sergio se eencaixa perfeitamente, so nao sei como faria para relacionar as duas classes, indicando que formam o mesmo interceptor.

Outra alternativa é criar anotações para dizer quem intercepta quem, a la vraptor 2. prefiro a primeira alternativa. que acham?

lavieri. E se em vez de Session voce receber SessionFactory no seu contstrutor, e esse mesmo interceptor abre a session e transação?

Lavieri

Editado:

Fica como sugestão a mudança na anotação de @Interceptors, adicionando um argumento onde seria possivel adicionar acceptors externos. que esta 2 posts abaixo.

Lavieri

Paulo Silveira:
a ideia do Sergio se eencaixa perfeitamente, so nao sei como faria para relacionar as duas classes, indicando que formam o mesmo interceptor.

Outra alternativa é criar anotações para dizer quem intercepta quem, a la vraptor 2. prefiro a primeira alternativa. que acham?

lavieri. E se em vez de Session voce receber SessionFactory no seu contstrutor, e esse mesmo interceptor abre a session e transação?

o problema de receber a factory para criar a session é que tenho q passala para os DAOs… que tem q ir parar no @Resource

no VRaptor 2 eu tinha um DaoInterceptor, que criava um daoFactory, passava ele pra logica, testava se havia a anotação de tranzação, abria tranzação se preciso, commitava e fechava a tranzação…

Com o VRaptor 3 as responsabilidades foram divididas, o que é legal, porem os interceptors passaram a abrir sempre, e acabam criando conexões demais…

Eu tinha inclusive uma anotação @DontInjectDaoFactory, ao encontrar essa anotação, no lugar de dar o @Out no daoFactory, eu simplismente passava null na variável… Isso era bastante Util, pois as vezes dentro da mesmo @Resource (o que na versão passada era @Compoment), vc carrega um formulário com dados em branco por exemplo, e nestas condições não há necessidade de abrir um DaoFactory.

Se preucupar com conexões é uma das grandes preucupações minha… no meu tomcat la da empresa tem mais de 50 sites rodando, não posso ficar abrindo conexão sem necessidade tem muito site no mesmo servidor.

Lavieri

Uma solução que resolveria fácil o problema ^^

mudar a classe @Interceptors para ficar assim

package br.com.caelum.vraptor;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import br.com.caelum.vraptor.ioc.Stereotype;

/**
 * Notifies vraptor to use this interceptor in the interceptor stack.<br>
 * Interceptors annotated are run in any order, therefore if the sequence is
 * important to your project, use an annotated InterceptorSequence.
 * 
 * @author Guilherme Silveira
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Stereotype
public @interface Intercepts {
	/**
	 * Define uma ou mais classes em separado que devem ser testada antes de criar o
	 * Interceptor para o escopo e testar o método accept do proprio interceptor
	 * @return
	 */
	Class&lt;? extends InterceptorAcceptor&gt;[] acceptors() default {};
}
Interface Adicionada.
import br.com.caelum.vraptor.resource.ResourceMethod;

public interface InterceptorAcceptor {
	public boolean accepts(ResourceMethod method);
}
Interface muada para extender InterceptorAcceptor
package br.com.caelum.vraptor.interceptor;

import br.com.caelum.vraptor.InterceptionException;
import br.com.caelum.vraptor.core.InterceptorStack;
import br.com.caelum.vraptor.resource.ResourceMethod;

/**
 * Whenever an interceptor accepts a resource method, its intercept method is
 * invoked to intercept the process of request parsing in order to allow the
 * software to do some specific tasks.<br>
 * Common usage for interceptors for end-users (end-programmers) are security
 * constraint checks, database session (open session in view) opening and much
 * more.
 *
 * If you have an interceptor A which depends on an interceptor B, i.e, interceptor
 * B must be executed before interceptor A, use {@link InterceptorSequence}
 * @author Guilherme Silveira
 */
public interface Interceptor extends InterceptorAcceptor  {

    void intercept(InterceptorStack stack, ResourceMethod method, Object resourceInstance) throws InterceptionException;

}

---------------

Exemplo de uso:

com esta solução meu Interceptor ficaria assim

//ai rodaria o TransactionAcceptor antes de isntanciar o TransactionInterceptor
@Intercepts(acceptors=TransactionAcceptor.class)
@RequestScoped
public class TransactionInterceptor implements Interceptor {
	private final Session session;

	public TransactionInterceptor(Session session) {
		this.session = session;
	}

	public void intercept(InterceptorStack stack, ResourceMethod method, Object instance) {
		//...
	}

	public boolean accepts(ResourceMethod method) {
		return true; //aceita qualquer recurso, desde que primeiro ele seja aceito pelo TransactionAcceptor
	}
}
@ApplicationScoped
public class TransactionAcceptor implements InterceptorAcceptor {
	public boolean accepts(ResourceMethod method) {
		return method
			.getMethod()
			.isAnnotationPresent(Transaction.class);
	}
}
Lucas_Cavalcanti

Idéias interessantes…

só não vejo muito o porquê de ter mais de um acceptor para um dado interceptor… faria um AND dos acceptors? ou um OR?

e não sei se isso vai resolver o problema… precisa testar se o Spring e/ou o Pico fazem a carga
das classes de forma preguiçosa (Lazy)… senão não adianta nada fazer isso…

Lavieri

lucascs:
Idéias interessantes…

só não vejo muito o porquê de ter mais de um acceptor para um dado interceptor… faria um AND dos acceptors? ou um OR?

e não sei se isso vai resolver o problema… precisa testar se o Spring e/ou o Pico fazem a carga
das classes de forma preguiçosa (Lazy)… senão não adianta nada fazer isso…

seriam AND e não ORs…

resolveria pois não precisa carregar o Interceptor, então não precisa de LAZY …

a idéia é…

antes de instanciar o Interceptor verificar sua anotação de acceptors, e ai, a ideia seria

public boolean verificaSeOInterceptorAceita(Class<? extends Interceptor> interceptorMetaDado
         , ResourceMethod resourceMethod) {

   //Resgata a anotação @Intercepts anotada no interceptor enviado.
   Intercepts intercepts = interceptorMetaDado.getAnnotation(Intercepts.class);

   //realiza um loop entre os acceptors declarados na anotação,
   //se a pessoa não declarar nada na anotação vai ter uma array vazia, não entrando no loop
   for (Class<? extends InterceptorAcceptor> acceptor : intercepts.acceptors()) {

      //resgata a implementação do acceptor.
      InterceptorAcceptor acceptorImpl = applicationContext.getBean(acceptor);

      //se não passar no acceptor  sai e retorna false, não executando assim o interceptor em questão.
      if (!acceptorImpl.accepts(resourceMethod))
         return false; //aqui sai do loop e nem instancia o Interceptor
   }

   //saindo do loop seria vez de instanciar o interceptor real
   Interceptor interceptor = applicationContext.getBean(interceptorMetaDado);

   //por último é testado o método accepts do proprio interceptor.
   return interceptor.accepts(resourceMethod);
}

Obs.: a variável applicationContext é uma instancia de VRaptorApplicationContext objeto que encontrei na calsse SpringBasedContainer.

eu encontrei a mairia dos lugares dentro dos códigos de vcs, só não achei bem a parte onde vocês testam se é para o interceptor aceitar…

Ate onde percebi nos códigos do vRaptor o método onde é feito o check já é enviado o Objeto Interceptor instanciado e não o metadado… enviado o metadado daria pra usar essa abordagem, onde seria possivel usar acceptors externos.

A idéia de poder usar outros acceptors externos seria dar + liberdade, e já que vai liberar acceptor externo já pode deixar suporte a mais de um, não limitando assim a forma de fazer…

assim seria possível barrar um acceptor de rodar sobre um método do recurso sem mesmo necessitar isntaciar o interceptor, via teste preliminares de acceptors externos declarados na anotação…

espero que a sugestão tenha ficado clara…

abraços

Lavieri

a ideia seria alterar o código mais ou menos nesta região

package br.com.caelum.vraptor.util.collections;

import java.lang.reflect.Method;

import br.com.caelum.vraptor.http.route.Route;
import br.com.caelum.vraptor.interceptor.Interceptor;
import br.com.caelum.vraptor.resource.HttpMethod;
import br.com.caelum.vraptor.resource.ResourceMethod;

import com.google.common.base.Predicate;

public class Filters {

	public static Predicate&lt;Interceptor&gt; accepts(final ResourceMethod method) {
		return new Predicate&lt;Interceptor&gt;() {
			public boolean apply(Interceptor interceptor) {
				return interceptor.accepts(method);
			}
		};
	}
	//...

no lugar de enviar um Interceptor seria enviado a classe do Interceptor....

public class Filters {

	public static Predicate&lt;Class&lt;? extends Interceptor&gt;&gt; accepts(final ResourceMethod method) {
		return new Predicate&lt;Class&lt;? extends Interceptor&gt;&gt;() {
			public boolean apply(Class&lt;? extends Interceptor&gt; interceptorMetaDado) {
				return verificaSeOInterceptorAceita(interceptorMetaDado,method);
			}
		};
	}
	//...

mudaria mais ou menos assim... não sei bem como seria, pois não deu pra estudar o código do VRaptor muito a fundo, mas seria algo + ou - neste trecho, evitando instanciar prematuramente o Interceptor.

Lavieri

Outra sugestão...

Uma outra forma seria de possibilitar as injeções de outra forma que não no construtor algo como permitir uma anoção
@PostAccepts no interceptor, onde neste método as injeções fossem feitas. Algo assim:

@Intercepts
public TransactionInterceptor extends Interceptor {
     private Session session;
     public void intercept(InterceptorStack stack, ResourceMethod method, Object instance) { ... }
     public boolean accepts(ResourceMethod method) { ... }
    
     @PostAccepts // &lt;== isso seria opicional. quem anotar vai ter injeções aqui após o accepts
     public void afterAccepts(Session session) {
          this.session = session;
     }
}
Lavieri

Apenas destacando, que dei uma solução temporária enquanto não vem a solução definitiva, pelo que conservei com o pessoal do VRaptor deve haver suporte a lazy,

enquanto isso dei uma implementada em um EntityManger Lazy, para quebrar meu galho...

Por favor perdoem meu inglês.... hehehe ... o HibernateUtils, e o ReflectionsUtils é so pra conveniencia, não vou colocar pq acredito que não precise

package br.com.tomazlavieri.vraptor.component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.annotation.PreDestroy;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import org.hibernate.ejb.HibernateEntityManager;
import org.hibernate.ejb.HibernateEntityManagerFactory;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.ioc.ComponentFactory;
import br.com.caelum.vraptor.ioc.RequestScoped;
import br.com.tomazlavieri.persistence.HibernateUtils;
import br.com.tomazlavieri.reflection.ReflectionUtils;

/**
 * Lazy {@link EntityManager} creator.
 * <BR>
 * <BR>Create enhanced lazy {@link EntityManager} to be open at first method call.
 * @author Tomaz Lavieri
 * @since 1.0
 */
@Component
@RequestScoped
public class EntityManagerCreator implements ComponentFactory&lt;EntityManager&gt; {
	/**
	 * Enhanced {@link EntityManager} will be open at lazy mode.
	 */
	private final EntityManager entityManager;
	/**
	 * {@link MethodInterceptor} will appropriately open the EntityManager at the first
	 * method call.
	 */
	private final Interceptor interceptor;
	
	/**
	 * Create one {@link EntityManagerCreator} injecting the {@link EntityManagerFactory}.
	 * @param factory the {@link EntityManagerFactory} used to create {@link EntityManager}.
	 */
	public EntityManagerCreator(EntityManagerFactory factory) {
		Enhancer enhancer = new Enhancer();
		if (factory instanceof HibernateEntityManagerFactory)
			enhancer.setInterfaces(new Class[]{HibernateEntityManager.class});
		else
			enhancer.setInterfaces(new Class[]{EntityManager.class});
		interceptor = new Interceptor(factory);
		enhancer.setCallback(interceptor);
		entityManager = (EntityManager)enhancer.create();
	}
	
	/**
	 * Rescue the enhanced {@link EntityManager}, the real {@link EntityManager} will be
	 * open lazy, after first method call.
	 */
	@Override
	public EntityManager getInstance() {
		return entityManager;
	}
	
	/**
	 * Destroy this {@link EntityManagerCreator} closing the {@link EntityManager}. 
	 */
	@PreDestroy
	public void destroy() {
		if (interceptor.hasEntityManager() && entityManager.isOpen()) {
			entityManager.close();
		}
	}
	
	/**
	 * The close method of {@link EntityManager}.
	 * @throws ExceptionInInitializerError its to never happens, but if this happen is cause the close method was not found.
	 */
	private static final Method CLOSE = ReflectionUtils.getMehtod(EntityManager.class, "close");
	/**
	 * The finalize method of {@link Object}.
	 * @throws ExceptionInInitializerError its to never happens, but if this happen is cause the finalize method was not found.
	 */
	private static final Method FINALIZE = ReflectionUtils.getMehtod(Object.class, "finalize");
	
	/**
	 * Entity Manager method interceptor.
	 * <br>
	 * <br>Intercepts all {@link EntityManager} method call, rescuing the real {@link EntityManager}
	 * or creating one if it need.<br>
	 * <br>
	 * All calls to {@link EntityManager#close()} are skiped, the {@link EntityManager} will be closed
	 * appropriately by the {@link EntityManagerCreator} at the @{@link PreDestroy} time. 
	 * @author Tomaz Lavieri
	 * @since 1.0
	 */
	private class Interceptor implements MethodInterceptor {
		/**
		 * The {@link EntityManagerFactory} used to lazy create the {@link EntityManager}.
		 */
		private final EntityManagerFactory emf;
		/**
		 * The real {@link EntityManager} created on the frist method call.
		 */
		private EntityManager entityManager;
		/**
		 * Create the {@link Interceptor} injecting the {@link EntityManagerFactory}.
		 * @param emf
		 */
		public Interceptor(EntityManagerFactory emf) {
			this.emf = emf;
		}
		@Override
		public Object intercept(Object obj, Method method, Object[] args,
				MethodProxy proxy) throws Throwable {
			
			if (method.equals(CLOSE) || (method.equals(FINALIZE) && !hasEntityManager()))
				return null; //skip
			
			try {
				return method.invoke(getEntityManager(), args);
			} catch (InvocationTargetException ex) {
				throw ex.getTargetException();
			}
		}
		/**
		 * Check if the real {@link EntityManager} is already created.
		 * @return
		 */
		private boolean hasEntityManager() {
			return entityManager != null;
		}
		/**
		 * Rescue or crate the real {@link EntityManager}.
		 * @return
		 */
		private EntityManager getEntityManager() {
			if (!hasEntityManager()) {
				entityManager = HibernateUtils.reconnectIfNeed(
									emf.createEntityManager()
								);
			}
			return entityManager;
		}
	}
}
G

Deixem-me dar agora meu pitaco sobre isso, conforme o post http://www.guj.com.br/posts/list/0/142375.java.

Penso que é um pouco custoso você inicializar o interceptor para testar se você vai aceitar ou não. Obvio que fazer um new em um objeto não é nada d+, o problema que se você possuir algo que seja injetado no construtor, como no meu caso do post acima, você terá mais alguns objetos inicializados para entrar no accepts e receber um false.

Claro que meu problema é um pouco mais simples que do Lavieri, mas fazer injeção lazy de certa forma pode custar um pouco mais processamento se pensarmos em soluções mais simples como por exemplo estaticamente avisar ao vraptor quem vai ou não aceitar. Quem sabe alguma anotação a nivel de classe com um accepts ou ignore?

@Intercepts(ignore={BarController.class, FooController.class}) // ignore é opcional, pode ter também um accepts opcional public class MyInterceptor { [...] }

Lavieri

Apenas para constar, a solução final esta aqui

http://vraptor.caelum.com.br/cookbook/poupando-recursos-lazy-dependency-injection/

ou no meu blog

A minha primeira ideia foi isso ai, colocar os testers fora do Interceptor, como opção… e roda-los antes de instanciar o interceptor, onde esses testers poderiam ser inclusives de escopos maiores que o Interceptor, como ApplicationScoped por exemplo…

Criado 11 de outubro de 2009
Ultima resposta 24 de out. de 2009
Respostas 12
Participantes 5