Journalisation avec Spring AOP

La programmation orientée Aspect nous permet de mettre en place des préoccupations qui peuvent s'appliquer à toutes les couches de nos applications. Dans cet article, nous allons voir comment mettre en place la journalisation automatique avec Spring AOP. En effet, à différents niveaux de nos applications, nous avons régulièrement besoin d'analyser les paramètres reçus dans une fonction, les paramètres en sortie, le cheminement suivi par le programme, ...

Plutôt que de mettre des logs dans toutes nos fonctions et d'utiliser uniquement le paramétrage de log4J pour changer le niveau d'affichage des logs, nous allons utiliser un des composants de Spring permettant la mise en place d'intercepteurs pour exécuter du code complémentaire: Spring AOP

Dans cet article, nous allons journaliser les appels de méthodes de chaque fonction, le temps d'exécution et les retours des méthodes. Afin de clarifier un point qui doit tout de suite vous venir à l'esprit, cette journalisation est effectivement relativement gourmande en temps d'exécution: analyse des intercepteurs, exécution du code supplémentaire, écriture fichier des logs, ... Nous ne mettrons donc en place ce système que sur des cas précis: analyse en développement, pistage en recette. Nous verrons qu'il est relativement simple de désactiver ce mécanisme de logs par paramétrage.

La mise en place d'un tel système va demander quelques opérations:
- Mise en place de Spring dans votre projet (nous considérons que c'est déjà fait)
- Ajout de Spring AOP et AspectJ
- Mise en place des intercepteurs et du code à exécuter
- Paramétrage du listener de Spring

Ajout de Spring AOP et AspectJ
Deux possibilités:
- si vous utilisez maven, vous aurez besoin de 2 dépendances:
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${org.springframework.version}</version>
    </dependency>

    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>${org.aspectj.verision}</version>
    </dependency>
- sinon, téléchargez le module AOP de spring ainsi que les librairies AspectJ. Dans nos projets, nous utilisons Spring 3.0.2 et AspectJ1.7.2.

Vous devez également avoir les librairies de log4J pour la journalisation.


Mise en place des intercepteurs et du code à exécuter
Nous allons maintenant créer une classe LoggerAop ainsi que le paramétrage des intercepteurs et du code à exécuter pour chaque méthode.
    @Component
    @Aspect
    public class LoggerAop {
    ...
    }
L'annotation @Component permettra à Spring de charger cette classe L'annotation @Aspect définira cette classe comme interepteur AspectJ

Dans cette classe, nous allons maintenant ajouter une méthode qui sera exécutée à chaque interception. Avant tout chose, nous définissions un logger log4J:
    private final Log myLogger = LogFactory.getLog(this.getClass());

Voici la signature de la méthode et le paramétrage de l'intercepteur:
    @Around("execution(* fr.mycompany..*.*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
    ...
    }
L'annotation @Around décrit le fonctionnement de notre intercepteur. A chaque exécution d'une méthode, notre méthode "log" sera exécuté avant le code de la méthode interceptée et se terminera après la fin de l'exécution de la méthode interceptée. Nous devrons donc indiquer dans le corps de notre méthode à quel moment l'exécution de la méthode interceptée doit s'effectuer. Voici le code complet de la méthode:
    // Mise en place d'un timer pour logger le temps d'exécution
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    // Exécution de la méthode interceptée
    Object retVal = joinPoint.proceed();

    // Arrêt du timer
    stopWatch.stop();

    // construction du message de log avec:
    // la classe, la signature de la méthode, le temps d'exécution
    //les paramétres en entrée et en sortie
    StringBuffer logMessage = new StringBuffer();
    logMessage.append(joinPoint.getTarget().getClass().getName());
    logMessage.append(".");
    logMessage.append(joinPoint.getSignature().getName());
    logMessage.append("(");
    // Ajout des paramètres en entrée
    Object[] args = joinPoint.getArgs();
    for (int i = 0; i < args.length; i++) {
         logMessage.append(args[i]).append(",");
    }
    if (args.length > 0) {
         logMessage.deleteCharAt(logMessage.length() - 1);
    }

    logMessage.append(")");
    logMessage.append(" execution time: ");
    logMessage.append(stopWatch.getTotalTimeMillis());
    logMessage.append(" ms");
    myLogger.info(logMessage.toString());
    if (retVal != null) {
         myLogger.info("returned value = " + retVal.toString());
    }
    return retVal;
Notre point d'interception (pointcut) et notre code à exécuter (code advice) sont maintenant prêts. Nous allons mettre en place le paramétrage nécessaire à Spring pour analyser les points d'ecécution.

Paramétrage du listener de Spring
Nous isolons le paramétrage des points d'interceptions dans un fichier spécifique. Cela nous permettra de le charger ou non suivant l'environnement: dev, recette ou production. Pour cela, il faut créer un fichier spring-aop.xml dans le répertoire WEB-INF et voici le contenu:
    <beans xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

         <aop:aspectj-autoproxy/>

    </beans>
Enfin, dans le web.xml, il suffit de rajouter le chargement de ce fichier au contexte de Spring:
    <context-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>/WEB-INF/spring-context.xml,WEB-INF/context-security-osgi.xml,WEB-INF/context-jmx.xml,WEB-INF/context-aop.xml</param-value>
    </context-param>
En fonction de l'environnement de destination, vous pourrez ainsi facilement charger ou non le contexte AOP. Au démarrage du projet, Spring détectera toutes les classes @Component utilisant les annotations @Aspect. Notre classe sera donc mise en place avant l'exécution de toutes méthodes présentes dans les classes gérées par Spring. Avec ce système, les classes non gérées par Spring ne disposeront pas de l'écritures des logs automatiques.

Stéphane Barthon

Aucun commentaire:

Enregistrer un commentaire


Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.