CDI Interceptor vs Spring AspectJ

CDI Interceptor vs Spring AspectJ

1. Introdução

O padrão do Interceptor é geralmente usado para adicionar novas funcionalidades ou lógica transversal a um aplicativo e possui suporte sólido em um grande número de bibliotecas.

Neste artigo, iremos cobrir e contrastar duas dessas bibliotecas principais: interceptores CDI e Spring AspectJ.

2. Configuração do projeto CDI Interceptor

O CDI é oficialmente suportado para Java EE, mas algumas implementações fornecem suporte para usar o CDI no ambiente Java SE. Weld pode ser considerado um exemplo de implementação de CDI que é compatível com Java SE.

Para usar o CDI, precisamos importar a biblioteca Weld em nosso POM:


    org.jboss.weld.se
    weld-se-core
    2.3.5.Final

A biblioteca Weld mais recente pode ser encontrada no repositórioMaven.

Vamos agora criar um interceptor simples.

3. Apresentando o CDI Interceptor

Para designar as classes que precisávamos interceptar, vamos criar a ligação do interceptor:

@InterceptorBinding
@Target( { METHOD, TYPE } )
@Retention( RUNTIME )
public @interface Audited {
}

Depois de definir a ligação do interceptor, precisamos definir a implementação real do interceptor:

@Audited
@Interceptor
public class AuditedInterceptor {
    public static boolean calledBefore = false;
    public static boolean calledAfter = false;

    @AroundInvoke
    public Object auditMethod(InvocationContext ctx) throws Exception {
        calledBefore = true;
        Object result = ctx.proceed();
        calledAfter = true;
        return result;
    }
}

Cada método@AroundInvoke leva um argumentojavax.interceptor.InvocationContext, retorna umjava.lang.Objecte pode lançar umException.

E assim, quando anotarmos um método com a nova interface@Audit,auditMethod será invocado primeiro e somente então o método de destino também continuará.

4. Aplicar o CDI Interceptor

Vamos aplicar o interceptor criado em alguma lógica de negócios:

public class SuperService {
    @Audited
    public String deliverService(String uid) {
        return uid;
    }
}

Criamos este serviço simples e anotamos o método que queríamos interceptar com a anotação@Audited.

Para habilitar o interceptor CDI, é necessário especificar o nome completo da classe no arquivobeans.xml, localizado no diretórioMETA-INF:


    
        com.example.interceptor.AuditedInterceptor
    

Para validar que o interceptor realmente funcionoulet’s now run the following test:

public class TestInterceptor {
    Weld weld;
    WeldContainer container;

    @Before
    public void init() {
        weld = new Weld();
        container = weld.initialize();
    }

    @After
    public void shutdown() {
        weld.shutdown();
    }

    @Test
    public void givenTheService_whenMethodAndInterceptorExecuted_thenOK() {
        SuperService superService = container.select(SuperService.class).get();
        String code = "123456";
        superService.deliverService(code);

        Assert.assertTrue(AuditedInterceptor.calledBefore);
        Assert.assertTrue(AuditedInterceptor.calledAfter);
    }
}

Neste teste rápido, primeiro obtemos o beanSuperService do contêiner, em seguida, invocamos o método de negóciosdeliverService nele e verificamos se o interceptorAuditedInterceptor foi realmente chamado validando suas variáveis ​​de estado.

Também temos métodos anotados@Beforee@After nos quais inicializamos e desligamos o contêiner do Weld, respectivamente.

5. Considerações CDI

Podemos destacar as seguintes vantagens dos interceptadores CDI:

  • É um recurso padrão da especificação Java EE

  • Algumas bibliotecas de implementações CDI podem ser usadas no Java SE

  • Pode ser usado quando projetamos limitações severas em bibliotecas de terceiros

As desvantagens dos interceptadores CDI são as seguintes:

  • Acoplamento apertado entre classe com lógica de negócios e interceptor

  • Difícil ver quais classes são interceptadas no projeto

  • Falta de mecanismo flexível para aplicar interceptores a um grupo de métodos

6. Spring AspectJ

O Spring suporta uma implementação semelhante da funcionalidade do interceptador usando a sintaxe AspectJ também.

Primeiro, precisamos adicionar as seguintes dependências Spring e AspectJ ao POM:


    org.springframework
    spring-context
    4.3.1.RELEASE


    org.aspectj
    aspectjweaver
    1.8.9

As versões mais recentes deSpring context,aspectjweaver podem ser encontradas no repositório Maven.

Agora podemos criar um aspecto simples usando a sintaxe de anotação AspectJ:

@Aspect
public class SpringTestAspect {
    @Autowired
    private List accumulator;

    @Around("execution(* com.example.spring.service.SpringSuperService.*(..))")
    public Object auditMethod(ProceedingJoinPoint jp) throws Throwable {
        String methodName = jp.getSignature().getName();
        accumulator.add("Call to " + methodName);
        Object obj = jp.proceed();
        accumulator.add("Method called successfully: " + methodName);
        return obj;
    }
}

Criamos um aspecto que se aplica a todos os métodos da classeSpringSuperService - que, para simplificar, tem a seguinte aparência:

public class SpringSuperService {
    public String getInfoFromService(String code) {
        return code;
    }
}

7. Spring AspectJ Aspect Apply

Para validar que esse aspecto realmente se aplica ao serviço, vamos escrever o seguinte teste de unidade:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = { AppConfig.class })
public class TestSpringInterceptor {
    @Autowired
    SpringSuperService springSuperService;

    @Autowired
    private List accumulator;

    @Test
    public void givenService_whenServiceAndAspectExecuted_thenOk() {
        String code = "123456";
        String result = springSuperService.getInfoFromService(code);

        Assert.assertThat(accumulator.size(), is(2));
        Assert.assertThat(accumulator.get(0), is("Call to getInfoFromService"));
        Assert.assertThat(accumulator.get(1), is("Method called successfully: getInfoFromService"));
    }
}

Neste teste, injetamos nosso serviço, chamamos o método e verificamos o resultado.

Esta é a aparência da configuração:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    @Bean
    public SpringSuperService springSuperService() {
        return new SpringSuperService();
    }

    @Bean
    public SpringTestAspect springTestAspect() {
        return new SpringTestAspect();
    }

    @Bean
    public List getAccumulator() {
        return new ArrayList();
    }
}

Um aspecto importante aqui na anotação@EnableAspectJAutoProxy - que permite o suporte para manipulação de componentes marcados com a anotação@Aspect de AspectJ, semelhante à funcionalidade encontrada no elemento XML do Spring.

8. Considerações sobre Spring AspectJ

Vamos apontar algumas das vantagens de usar Spring AspectJ:

  • Os interceptores são dissociados da lógica de negócios

  • Os interceptadores podem se beneficiar da injeção de dependência

  • O Interceptor possui todas as informações de configuração em si

  • Adicionar novos interceptores não exigiria aumentar o código existente

  • O Interceptor possui mecanismo flexível para escolher quais métodos interceptar

  • Pode ser usado sem Java EE

E, claro, algumas das desvantagens:

  • Você precisa conhecer a sintaxe do AspectJ para desenvolver interceptores

  • A curva de aprendizado para os interceptores AspectJ é maior que para os interceptadores CDI

9. CDI Interceptor VS Spring AspectJ

Se o seu projeto atual usa o Spring, considerar o Spring AspectJ é uma boa escolha.

Se você estiver usando um servidor de aplicativos completo ou seu projeto não usar Spring (ou outros frameworks, por exemplo, Google Guice) e for estritamente Java EE, então não há mais nada do que escolher o interceptor CDI.

10. Conclusão

Neste artigo, abordamos duas implementações do padrão interceptador: CDI interceptor e Spring AspectJ. Cobrimos vantagens e desvantagens de cada uma delas.

O código-fonte para exemplos deste artigo pode ser encontrado em nosso repositório emGitHub.