Annotations AspectJ avec Spring

Avec l’AOP[1], on ajoute des comportements aux classes et méthodes existante, en se basant sur leurs métadonnées : nom, package, annotations,…​

Ainsi la gestion transactionnelle peut aussi être réglée par cette technique, de même que l’ajout de traces ou la mesure de performances.

Configuration

Pour commencer, il faut activer la prise en compte d’AspectJ dans une classe de configuration.

@Configuration
@EnableAspectJAutoProxy
public class ApplicationConfiguration {
}

Configuration

La classe d’aspect est implémentée avec des annotations d'AspectJ. Elle doit aussi être un bean Spring.

@Aspect @Component
public class LoggingInterceptor {
}

Dans cette classe, on peut définir les pointcuts et les advices.

Un pointcuts définit une règle d’exécution des aspects, par exemple :

  • à l’exécution d’une méthode dont le nom commence par find…​,

  • à l’exécution d’une méthode annotée par @Logging

Un point de jonction définit la façon dont le traitement va s’intégrer par rapport au pointcut, par exemple :

  • avant le pointcut,

  • après que le pointcut ait produit un résultat,

  • après que le pointcut ait levé une exception

Un advice, parfois traduit en greffon, définit le traitement qui doit être exécuté à un point de jonction d’un pointcut.

Advice

Pour définir un advice, on utilise une de ces annotation d’AspectJ sur une méthode de la classe d’aspect :

  • @Before

    • avant le pointcut,

    • la méthode d'advice a un paramètre optionnel de type JoinPoint qui représente les métadonnées du point de jonction.

  • @After

    • après le pointcut,

    • la méthode d'advice a un paramètre optionnel de type JoinPoint qui représente les métadonnées du point de jonction.

  • @AfterReturning

    • après le pointcut, sans levée d’exception

    • la méthode d'advice a un paramètre optionnel de type JoinPoint qui représente les métadonnées du point de jonction,

    • la méthode d'advice a un paramètre optionnel pour le retour du pointcut.

  • @AfterThrowing

    • après le pointcut, avec une levée d’exception,

    • la méthode d'advice a un paramètre optionnel de type JoinPoint qui représente les métadonnées du point de jonction,

    • la méthode d'advice a un paramètre optionnel pour l’exception levée par le pointcut.

  • @Around

    • autour du pointcut (avant et après)

    • la méthode d'advice a un paramètre de type ProceedingJoinPoint qui représente les métadonnées du point de jonction,

    • la méthode proceed(…​) doit être appelée explicitement sur le point de jonction pour qu’il soit effectivement exécuté.

Le paramètre passé à l’annotation définit l’expression du pointcut ou fait référence à un pointcut définit ailleurs.

@Component
@Aspect
public class LoggingInterceptor {

  @Before("execution(* info.jtips.spring.dao.*.find*(..))")
  public void logDaoFind() throws Throwable {
    System.out.println("==> Début dao.find");
  }

  @Around("service()")
  public Object logService(ProceedingJoinPoint point) throws Throwable {
    String methodName = point.getSignature().toString();
    System.out.printf("==> Début de service : '%s'%n", methodName);
    Object obj = point.proceed();
    System.out.printf("<== Fin de service : '%s'%n", methodName);

    return obj;
  }

  //...

}

Pour @AfterReturning et @AfterThrowing, il faut préciser le nom du paramètre qui recevra le retour ou l’exception.

  @AfterThrowing(
        pointcut = "execution(* info.jtips.spring..(..))",
        throwing = "exception")
  public void logProblem(JoinPoint point, Exception exception) {
    System.out.printf("==> Problème : %.100s sur '%s'%n", exception, point.getSignature());
  }

  @AfterReturning(
        pointcut = "execution(* info.jtips.spring..(..))",
        returning = "result")
  public void logReturn(JoinPoint point, Product result) {
    System.out.printf("==> Résultat : %.100s de '%s'%n", result, point.getSignature());
  }

Sans cette précision, Spring et AspectJ lèvent une exception à l’initialisation, dont le message n’est pas très parlant.

java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut
    at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:319)
    at org.springframework.aop.aspectj.AspectJExpressionPointcut...
    at ...

Pointcut

Les pointcuts sont définis directement dans les annotations des advices, comme dans la méthode logDaoFind() de l’exemple ci-dessus, ou à part pour être réutilisées entre plusieurs advices.

@Aspect @Component
public class LoggingInterceptor {

  @Around("service()")
  public Object logService(ProceedingJoinPoint point) throws Throwable {
    //...
  }

  @Pointcut("execution(* info.jtips.spring.service..(..))")
  private void service() {
  }

}

L’annotation @Pointcut s’utilise sur une méthode vide. Elle sert juste à définir le nom du pointcut.

L’expression qui définit le pointcut est écrite dans le langage d’AspectJ. En réalité, Spring AOP supporte uniquement une partie des primitives du langage.

Dans l’exemple ci-dessus, le pointcut execution(* info.jtips.spring.service..(..)) représente l’exécution de n’importe quelle méthode de n’importe quelle classe du paquetage info.jtips.spring.service.

Pointcut sur annotation

Plutôt que de définir un pointcut par le nom des paquetages, classes et méthodes , il est possible de le définir par la présence d’une annotation. L’annotation peut être recherchée sur une classe, avec la primitive @within, ou sur une méthode avec @annotation.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Logging {
}
@Aspect @Component
public class LoggingAnnotationInterceptor {

  @Pointcut("@annotation(Logging) || @within(Logging)")
  private void logging() {
  }

}

Avec un tel pointcut, on peut créer des advices qui vont opérer sur les méthodes annotées avec @Logging et celles qui sont dans des classes annotées avec @Logging.

Cette technique peut s’utiliser avec des annotations existantes. Par exemple, on peut définir un comportement pour toutes les méthodes des classes annotées par @Service.

@Aspect @Component
public class LoggingAnnotationInterceptor {

  @Pointcut("@within(org.springframework.stereotype.Service)")
  private void service() {
  }

}

Ce pointcut peut servir de base à une gestion des transactions avec les objectifs définis dans le paragraphe sur les annotations de transaction.


1. Aspect Oriented Programming