Intercepteur CDI contre Spring AspectJ

1. Introduction

Le modèle Interceptor est généralement utilisé pour ajouter de nouvelles fonctionnalités ou logiques transversales dans une application, et bénéficie d'un support solide dans un grand nombre de bibliothèques.

Dans cet article, nous allons couvrir et comparer deux de ces bibliothèques majeures: les intercepteurs CDI et Spring AspectJ.

2. Configuration du projet CDI Interceptor

CDI est officiellement pris en charge pour Jakarta EE, mais certaines implémentations permettent d'utiliser CDI dans l'environnement Java SE. Weld peut être considéré comme un exemple d'implémentation CDI pris en charge dans Java SE.

Pour utiliser CDI, nous devons importer la bibliothèque Weld dans notre POM:

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

La bibliothèque Weld la plus récente se trouve dans le référentiel Maven.

Créons maintenant un simple intercepteur.

3. Présentation de l'intercepteur CDI

Afin de désigner les classes que nous devions intercepter, créons la liaison d'intercepteur:

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

Après avoir défini la liaison de l'intercepteur, nous devons définir l'implémentation réelle de l'intercepteur:

@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; } }

Chaque méthode @AroundInvoke prend un argument javax.interceptor.InvocationContext , renvoie un java.lang.Object et peut lever une exception .

Ainsi, lorsque nous annotons une méthode avec la nouvelle interface @Audit , auditMethod sera appelé en premier, et alors seulement la méthode cible continuera également.

4. Appliquez l'intercepteur CDI

Appliquons l'intercepteur créé à une logique métier:

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

Nous avons créé ce service simple et annoté la méthode que nous voulions intercepter avec l' annotation @Audited .

Pour activer l'intercepteur CDI, il faut spécifier le nom complet de la classe dans le fichier beans.xml , situé dans le répertoire META-INF :

  com.baeldung.interceptor.AuditedInterceptor  

Pour valider que cet intercepteur a bien fonctionné, exécutons maintenant le test suivant :

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); } }

Dans ce test rapide, nous obtenons d'abord le bean SuperService du conteneur, puis nous invoquons la méthode métier deliveryService sur celui-ci et vérifions que l'intercepteur AuditedInterceptor a été réellement appelé en validant ses variables d'état.

Nous avons également les méthodes annotées @Before et @After dans lesquelles nous initialisons et arrêtons respectivement le conteneur Weld.

5. Considérations relatives au CDI

Nous pouvons souligner les avantages suivants des intercepteurs CDI:

  • C'est une caractéristique standard de la spécification Jakarta EE
  • Certaines bibliothèques d'implémentations CDI peuvent être utilisées dans Java SE
  • Peut être utilisé lorsque notre projet a des limitations sévères sur les bibliothèques tierces

Les inconvénients des intercepteurs CDI sont les suivants:

  • Couplage étroit entre classe avec logique métier et intercepteur
  • Difficile de voir quelles classes sont interceptées dans le projet
  • Absence de mécanisme flexible pour appliquer les intercepteurs à un groupe de méthodes

6. Aspect du ressortJ

Spring prend également en charge une implémentation similaire de la fonctionnalité d'intercepteur à l'aide de la syntaxe AspectJ.

Nous devons d'abord ajouter les dépendances Spring et AspectJ suivantes à POM:

 org.springframework spring-context 5.2.8.RELEASE   org.aspectj aspectjweaver 1.9.2 

Les versions les plus récentes du contexte Spring, aspectjweaver, se trouvent dans le référentiel Maven.

Nous pouvons maintenant créer un aspect simple en utilisant la syntaxe d'annotation AspectJ:

@Aspect public class SpringTestAspect { @Autowired private List accumulator; @Around("execution(* com.baeldung.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; } }

Nous avons créé un aspect qui s'applique à toutes les méthodes de la classe SpringSuperService - qui, pour simplifier, ressemble à ceci:

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

7. Spring AspectJ Aspect Appliquer

Afin de valider que cet aspect s'applique vraiment au service, écrivons le test unitaire suivant:

@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")); } }

Dans ce test, nous injectons notre service, appelons la méthode et vérifions le résultat.

Voici à quoi ressemble la configuration:

@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(); } }

One important aspect here in the @EnableAspectJAutoProxy annotation – which enables support for handling components marked with AspectJ's @Aspect annotation, similar to functionality found in Spring's XML element.

8. Spring AspectJ Considerations

Let's point out a few of the advantages of using Spring AspectJ:

  • Interceptors are decoupled from the business logic
  • Interceptors can benefit from dependency injection
  • Interceptor has all the configuration information in itself
  • Adding new interceptors wouldn't require augmenting existing code
  • Interceptor has flexible mechanism to choose which methods to intercept
  • Can be used without Jakarta EE

And of course a few of the disadvantages:

  • You need to know the AspectJ syntax to develop interceptors
  • La courbe d'apprentissage des intercepteurs AspectJ est plus élevée que celle des intercepteurs CDI

9. Intercepteur CDI contre Spring AspectJ

Si votre projet actuel utilise Spring, alors considérer Spring AspectJ est un bon choix.

Si vous utilisez un serveur d'applications à part entière, ou si votre projet n'utilise pas Spring (ou d'autres frameworks, par exemple Google Guice) et est strictement Jakarta EE, il ne reste plus qu'à choisir l'intercepteur CDI.

10. Conclusion

Dans cet article, nous avons couvert deux implémentations du modèle d'intercepteur: l'intercepteur CDI et Spring AspectJ. Nous avons couvert les avantages et les inconvénients de chacun d'eux.

Le code source des exemples de cet article se trouve dans notre référentiel sur GitHub.