VRaptor

Quando 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 utilizamos uma abordagem onde o interceptador define quem será interceptado, muito mais próxima da abordagens de interceptação que aparecem em sistemas baseados em AOP (Programação Orientada a Aspectos). O escopo padrão de um interceptor é RequestScoped.

Portanto, para interceptar uma requisição, basta 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.

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 quaisquer dependências por meio de Injeção de Dependências.

@Intercepts
@RequestScoped
public class Log {

    @Inject
    private HttpServletRequest request;

    @AroundCall
    public void intercept(SimpleInterceptorStack stack) {
        System.out.println("Interceptando " + request.getRequestURI());
        // código a ser executado antes da lógica

        stack.next(); // continua a execução
    }
}

Para executar um código apenas antes ou depois da execução do controller, podemos usar as anotações @BeforeCall e @AfterCall:

@Intercepts
@RequestScoped
public class Log {
    @BeforeCall
    public void before() {
        // código a ser executado antes da lógica
    }
    @AfterCall
    public void after() {
        // código a ser executado depois da lógica
    }
}

Repare que não é mais obrigatório implementar a interface Interceptor, basta criar a classe e usar as anotações acima.

Definindo quando interceptar

No exemplo acima todas as requisições são interceptadas. Podemos então definir que iremos interceptar apenas os métodos que possuem uma anotação chamada @Audit. Para isso basta implementar um método que retorne um booleancom a condição necessária e anotá-lo com @Accepts.

@Accepts
public boolean accepts(ControllerMethod method) {
    return method.containsAnnotation(Audit.class);
}

No VRaptor4 também temos outra maneira de definir a condição de aceitação. O exemplo acima é um caso bem comum e acabamos tendo de repetir esse código muitas vezes. Para essas situações você pode usar a annotation @AcceptsWithAnnotations.

@AcceptsWithAnnotations(Audit.class)
public class AuditInterceptor {
    
    @AroundCall
    public void around(SimpleInterceptorStack stack){
        stack.next();
    }
}

Criando o seu Accepts customizado

Logo acima usamos o exemplo do Accepts customizado. Este é um que já vem pronto VRaptor. Mas nada impede de você criar o seu para as suas necessidades. O fluxo é bem parecido com o de criação de uma validação customizada para a BeanValidation. Primeiro você deve criar sua Annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@AcceptsConstraint(WithAnnotationAcceptor.class)
@Inherited
public @interface AcceptsWithAnnotations {
    public Class<? extends Annotation>[] value();
}

Perceba que usamos a Annotation @AcceptsConstraint para indicar qual a classe responsável por fazer a verificação. Agora precisamos implementar essa classe.

public class WithAnnotationAcceptor implements AcceptsValidator<AcceptsWithAnnotations> {

    @Override
    public boolean validate(ControllerMethod controllerMethod, ControllerInstance instance) {
        //aqui entra sua logica de validação do método ou instancia do controller.
        return true;
    }

    @Override
    public void initialize(AcceptsWithAnnotations annotation) {
        //aqui você pode acessar as informações contidas na annotation customizada.
        this.allowedTypes = Arrays.asList(annotation.value());
    }
}

Esta classe só precisa implemetar a interface AcceptsValidator<T> onde T é o tipo da sua Annotation customizada. Dessa maneira você pode criar as suas condições customizadas e disponibilizá-las para todos os projetos que você precisa.

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 { ... }

quanto com:

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

Você pode especificar mais de um Interceptor:

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

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