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.