07 Interceptando recursos anotados
por Tomaz Lavieri, no blog
Em primeiro lugar, gostaria de tecer meus mais sinceros elogios a equipe VRaptor, a versão 3 está 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
@Resource
public class ProdutoController {
private final DaoFactory factory;
private final ProdutoDao dao;
public ProdutoController(DaoFactory factory) {
this.factory = factory;
this.dao = factory.getProdutoDao();
}
public List<Produto> listar() {
return dao.list();
}
public Produto atualizar(Produto produto) {
try {
factory.beginTransaction();
produto = dao.update(produto);
factory.commit();
return produto;
} catch (DaoException ex) {
factory.rollback();
throw ex;
}
}
public Produto adicionar(Produto produto) {
try {
factory.beginTransaction();
produto = dao.store(produto);
factory.commit();
return produto;
} catch (DaoException ex) {
factory.rollback();
throw ex;
}
}
}
Agora, nós 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.
import org.hibernate.Session;
import org.hibernate.Transaction;
import br.com.caelum.vraptor.Intercepts;
import br.com.caelum.vraptor.core.InterceptorStack;
import br.com.caelum.vraptor.interceptor.Interceptor;
import br.com.caelum.vraptor.resource.ResourceMethod;
@Intercepts
public class TransactionInterceptor implements Interceptor {
private final Session session;
public HibernateTransactionInterceptor(Session session) {
this.session = session;
}
public void intercept(InterceptorStack stack, ResourceMethod method,
Object instance) {
Transaction transaction = null;
try {
transaction = session.beginTransaction();
stack.next(method, instance);
transaction.commit();
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
public boolean accepts(ResourceMethod method) {
return true; //aceita todas as requisições
}
}
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:
public Produto atualizar(Produto produto) {
return dao.update(produto);
}
public Produto adicionar(Produto produto) {
return dao.store(produto);
}
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:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Usado para garantir que um determinado recurso interceptado seja executada em um
* escopo de transação.
* @author Tomaz Lavieri
* @since 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Transactional {}
Agora precisamos marcar os pontos onde queremos que o escopo seja transacional com esta anotação.
@Resource
public class ProdutoController {
private final ProdutoDao dao;
public ProdutoController(ProdutoDao dao) {
this.dao = dao;
}
public List<Produto> listar() {
return dao.list();
}
@Transactional
public Produto atualizar(Produto produto) {
return dao.update(produto);
}
@Transactional
public Produto adicionar(Produto produto) {
return dao.store(produto);
}
}
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:
public boolean accepts(ResourceMethod method) {
return method
.getMethod() //metodo anotado
.isAnnotationPresent(Transactional.class)
|| method
.getResource() //ou recurso anotado
.getType()
.isAnnotationPresent(Transactional.class);
}
Pronto, agora somente os métodos com a anotação @Transacional são executados em escopo de transação e economizamos linhas e linhas de códigos de try{commit}catch{rollback throw ex}