06 Interceptadores

Para quê interceptar?

O uso de interceptadores é feito para executar alguma tarefa antes e/ou depois de uma lógica de negócios, sendo os usos mais comuns a validação de dados, controle de conexão e transação do banco, log e criptografia/compactação de dados.

Como interceptar?

No VRaptor 3 utilizamos uma abordagem onde o interceptador define quem será interceptado, muito mais próxima a abordagens de interceptação que aparecem em sistemas baseados em AOP (programação orientada a aspectos) do que a abordagem da versão anterior do vraptor. Portanto, para interceptar uma requisição, basta implementar a interface Interceptor e anotar a classe com a anotação @Intercepts. Assim como qualquer outro componente, com o uso das anotações de escopo você pode dizer em que escopo o interceptador será armazenado.

public interface Interceptor {

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

    boolean accepts(ResourceMethod method);

}

Exemplo simples

A classe a seguir mostra um exemplo de como interceptar todas as requisições em um escopo de requisição e simplesmente mostrar na saída do console o que está sendo invocado. Lembre-se de que o interceptador é um componente como qualquer outro e pode receber em seu construtor quaisquer dependências por meio de Injeção de Dependências.

@Intercepts
@RequestScoped
public class Log implements Interceptor {

    private final HttpServletRequest request;

    public Log(HttpServletRequest request) {
        this.request = request;
    }

    /*
     * Este interceptor deve interceptar o method dado? Neste caso vai interceptar
     * todos os métodos.
     * method.getMethod() retorna o método java que está sendo executado
     * method.getResourceClass().getType() retorna a classe que está sendo executada
     */
    public boolean accepts(ResourceMethod method) {
        return true;
    }

    public void intercept(InterceptorStack stack, ResourceMethod method,
                        Object resourceInstance) throws InterceptionException {
        System.out.println("Interceptando " + request.getRequestURI());
        // código a ser executado antes da lógica

        stack.next(method, resourceInstance); // continua a execução

        // código a ser executádo depois da lógica
    }

}

Exemplo com Hibernate

Provavelmente, um dos usos mais comuns do Interceptor é para a implementação do famigerado pattern Open Session In View, que fornece uma conexão com o banco de dados sempre que há uma requisição para sua aplicação. E ao fim dessa requisição, a conexão é liberada. O grande ganho disso é evitar exceções como LazyInitializationException no momento da renderização dos JSPs. Abaixo, está um simples exemplo, que para todas as requisições abre uma transação com o banco de dados. E ao fim da execução da lógica e da exibição da página para o usuário, commita a transação e logo em seguida fecha a conexão com o banco.

@RequestScoped
@Intercepts
public class DatabaseInterceptor implements br.com.caelum.vraptor.Interceptor {

    private final Database controller;

    public DatabaseInterceptor(Database controller) {
        this.controller = controller;
    }

    public void intercept(InterceptorStack stack, ResourceMethod method, Object instance)
        throws InterceptionException {
        try {
            controller.beginTransaction();
            stack.next(method, instance);
            controller.commit();
        } finally {
            if (controller.hasTransaction()) {
                controller.rollback();
            }
            controller.close();
        }
    }

    public boolean accepts(ResourceMethod method) {
        return true;
    }

}

Dessa forma, no seu Recurso, bastaria o seguinte código para utilizar a conexão disponível:

@Resource
public class FuncionarioController {

    public FuncionarioController(Result result, Database controller) {
        this.result = result;
        this.controller = controller;
    }

    @Post
    @Path("/funcionario")
    public void adiciona(Funcionario funcionario) {
        controller.getFuncionarioDao().adiciona(funcionario);
        ...
    }
}

Como garantir ordem: after e before

Se é preciso garantir a ordem de execução de um conjunto de interceptors, você pode usar os atributos after e before da anotação @Intercepts. Suponha que o PrimeiroInterceptor tenha que rodar antes do SegundoInterceptor, então você pode configurar isso tanto com:

@Intercepts(before=SegundoInterceptor.class)
public class PrimeiroInterceptor implements Interceptor {
    ...
}

quanto com:

@Intercepts(after=PrimeiroInterceptor.class)
public class SegundoInterceptor implements Interceptor {
    ...
}

Você pode especificar mais de um Interceptor:

@Intercepts(after={PrimeiroInterceptor.class, SegundoInterceptor.class},
            before={QuartoInterceptor.class, QuintoInterceptor.class})
public class TerceiroInterceptor implements Interceptor {
    ...
}

O VRaptor lançará uma exception se existir um ciclo na ordem dos interceptors, então tenha cuidado.

Interagindo com os interceptors do VRaptor

O VRaptor possui sua própria sequência de interceptor, e você pode definir a ordem dos seus interceptors baseados na do VRaptor. Aqui estão os principais interceptors do VRaptor e o que eles produzem:

Se você precisa executar um Interceptor após o ExecuteMethodInterceptor, você deve setar o atributo before, para evitar ciclos. O ForwardToDefaultViewInterceptor é um bom valor, já que nenhum outro interceptor pode rodar depois dele:

@Intercepts(after=ExecuteMethodInterceptor.class,
            before=ForwardToDefaultViewInterceptor.class)